HMCache 版本 0.2.7

HMCache 版本 0.2.7

测试已测试
语言语言 Obj-CObjective-C
许可证 MIT
发布上次发布2018年11月

lixian 维护。



HMCache 版本 0.2.7

  • 作者
  • lxian1988

概述

Version Platform License

HMCache 是适用于 OS X 和 iOS 应用程序数据持久化的库,包含一些其他有用功能。它是从头开始编写的,并由苹果的 NSCoding/NSCoder 驱动。HMCache 实现了一套对象,这些对象协同工作以提供非常方便的持久化 API。这些对象由以下几种:

  • HMObject 是一个基类,其子类可以自动扫描自身及其超类(至 NSObject)的属性名称,并将每个版本的类键保存到磁盘上。这些版本的记录将用于通过 NSCoding 方法 initWithCoder:encodeWithCoder: 序列化和反序列化 HMObject 子类的实例,并从早期序列化数据迁移到当前版本类结构并初始化一个实例。
  • HMCacheManager 是一个文件缓存引擎,提供键值风格 API。它是单例。最低 API 用于使用一个键名写入一个 NSData 到文件,并使用相同的键从磁盘读取一个 NSData。它还提供了 'group' 的概念,实际上是一个目录。使用具有键和组的读写 API 可以在特定组中排序键。
  • HMMigrationData 用于从 HMObject 子类实例迁移序列化缓存的数据。在反序列化缓存数据并检测到数据是来自旧版本时,将会新建一个 HMMigrationData 对象,并将所有 HMObject 子类对象的缓存值读取并设置为 HMMigration 数据实例。然后,HMMigration 数据实例将通过 - (BOOL)migrateWithData:(HMMigrationData *)migrationData fromVersion:(NSString *)version 方法传递给 HMObject 子类。子类必须根据旧版本和当前版本之间的差异,修改、删除用于迁移目的的关键值。

额外内置功能

  • 自动实现子类的 NSCopying 协议。
  • 实现 isEqual:
  • 实现 - (NSUInteger)hash
  • 自动实现一个 JSON 格式的 - (NSString *)description- (NSString *)debugDescription
  • 上述特性是否支持 分类 属性。

开始使用

下载或查看HMCache的最新版本,然后将整个“HMCache”子文件夹添加到您的Xcode项目中。

或者,您可以使用CocoaPods安装HMCache,只需在您的Podfile中添加以下行

pod "HMCache", "~> 0.1.5"

最后运行 $ pod install

Hello World

HMObject子类

从HMObject派生与从其他Cocoa类如NSObject派生无异。

#import "HMObject"

@interface FooObject : HMObject

@property (nonatomic, copy) NSString *string;
@property (nonatomic, strong) NSNumber *number;

@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) NSArray *array;
@property (nonatomic, strong) BarObject *barObject;

@end

缓存和恢复

NSString *key = @"key";
    
FooObject *foo = [FooObject objectInCacheForKey:key];
if (foo) {
	NSLog(@"cached foo is: %@", foo);
}
else {
	foo = [FooObject new];
	NSLog(@"new foo is: %@", foo);
        
	[foo cacheForKey:key];
}

分类属性

在分类接口中声明属性与常规声明相同。

@interface FooObject (Category)

@property (nonatomic, strong) NSString *categoryString;

@end

然后使用运行时实现属性的setter和getter,并通过方法 - (void)registerPropertyName:(NSString *)propertyName - (void)registerPropertyName:(NSString *)propertyName withCategoryName:(NSString *)categoryName 注册属性。

@implementation FooObject (Category)

- (void)setCategoryString:(NSString *)categoryString {
    objc_setAssociatedObject(self, "categoryString", categoryString, OBJC_ASSOCIATION_COPY_NONATOMIC);
    [self registerPropertyName:@"categoryString"];
}
- (NSString *)categoryString {
    return objc_getAssociatedObject(self, "categoryString");
}

@end

迁移

何时进行迁移?

  • 更改属性名称。
  • 更改属性类型。
  • 同时更改属性名称和类型。
  • 删除属性。

添加属性不需要迁移。

通用属性变更迁移

方法 - (BOOL)migrateWithData:(HMMigrationData *)migrationData fromVersion:(NSString *)version 用于从旧版本数据迁移到当前版本。默认情况下,使用 XCode 项目的版本作为 HMObject 子类的类版本。如果有多个版本已经发布,迁移代码必须按时间顺序编写。

- (BOOL)migrateWithData:(HMMigrationData *)migrationData fromVersion:(NSString *)version {
    
    if ([version isEqualToString:@"1.0.0"]) {
        
        // change name
        [migrationData replaceKey:@"string" withKey:@"changeNameString"];
        
        // change type
        NSDate *date = [migrationData objectForKey:@"date"];
        [migrationData setObject:@([date timeIntervalSince1970]) forKey:@"date"];
        
        // delete
        [migrationData removeObjectForKey:@"integer"];
        
        // delete bar object, move barobject.string to fooobject.addString
        HMMigrationData *barObject = [migrationData objectForKey:@"barObject"];
        [migrationData removeObjectForKey:@"barObject"];
        NSString *barObjectString = [barObject objectForKey:@"string"];
        [migrationData setObject:barObjectString forKey:@"addString"];
    }

    if ([version isEqualToString:@"1.0.1"] ||
		[version isEqualToString:@"1.0.2"] ||) {
        
        // delete
        [migrationData removeObjectForKey:@"array"];
    }
    
    ...
    
    return YES;
}

提示

  1. 返回 YES 表示迁移成功,而 NO 表示失败或放弃。返回 NO 将导致该版本的类模式从磁盘删除,因此无法为该版本进行未来的迁移操作。
  2. 如果从特定版本迁移没有需要执行的操作(例如,没有更改或仅添加了某些属性),则只需返回 YES。但是,如果您为旧版本实现过迁移代码,则不需要迁移的版本必须使用其前一版本的迁移代码。就像上面的迁移示例代码一样,版本 '1.0.1' 和 '1.0.2' 有相同的类模式,因此不需要从 '1.0.1' 迁移到 '1.0.2',但它们必须使用相同的迁移代码以迁移到/从其他版本。

删除 HMObject 子类的迁移

如果在特定版本之后已删除旧版本创建的 HMObject 子类,则它将在迁移方法 - (BOOL)migrateWithData:(HMMigrationData *)migrationData fromVersion:(NSString *)version 中被 HMMigrationData 实例替换。例如,FooObject 和 BarObject 都是 HMObject 的子类,FooObject 在版本 "1.0.0" 中拥有名为 "barObject" 的属性,类型为 BarObject,然后在版本 "1.0.1" 中删除了 BarObject。我们想要读取 "barObject" 属性的值并从 FooObject 中删除此键,这里是我们的做法

// Current version is "1.0.1"
- (BOOL)migrateWithData:(HMMigrationData *)migrationData fromVersion:(NSString *)version {
    if ([version isEqualToString:@"1.0.0"]) {
        HMMigrationData *barObject = migrationData[@"barObject"];
        NSDate *barObjectDate = barObject[@"date"];
        [migrationData replaceKey:@"barObject" withKey:@"barObjectDate" object:barObjectDate];
    }
    
    return YES;
}

很明显,整个 "barObject" 值已被另一个 HMMigrationData 替换,除了表示根 FooObject 实例的那个。在版本 "1.0.0" 中由 BarObject 拥有并由 barObject HMMigrationData 持有的属性 "date"。我们读取它并将旧的 "barObject" 键替换为新的键 "barObjectDate"。

描述

HMObject 重写了 NSObject 的 - (NSString *)description- (NSString *)debugDescription 方法,以便在 XCode 控制台输出 JSON 格式的数据。它是一个子类,自动获得这种功能。以下是一个示例

- (void)testDescription {
    
    NSLog(@"Testing description ... \n\n");
    
    FooObject *foo = [FooObject new];
    NSLog(@"new foo is: %@", foo);
    
    NSMutableArray *mutableArray = [NSMutableArray array];
    for (int i = 0; i < 3; i++) {
        FooObject *object = [FooObject new];
        [mutableArray addObject:object];
    }
    
    NSLog(@"array is: %@", mutableArray.description);
    
    NSDictionary *dictionary = @{@"1" : [FooObject new],
                                 @"2" : [SubFooObject new]};
    NSLog(@"dictionary is: %@", dictionary.description);
}

在 XCode 控制台中输出的内容如下

2017-02-06 16:03:40.355 HMCacheDemo[10959:1016121] Testing description ... 

2017-02-06 16:03:40.355 HMCacheDemo[10959:1016121] new foo is: 
{
  "*FooObject" : {
    "(TestCategory)categoryString" : "This is a NSString in category",
    "integer" : 1,
    "barObjectDate" : null,
    "string" : "I am a foo!"
  }
}
2017-02-06 16:03:40.356 HMCacheDemo[10959:1016121] array is: 
[
  {
    "*FooObject" : {
      "(TestCategory)categoryString" : "This is a NSString in category",
      "integer" : 1,
      "barObjectDate" : null,
      "string" : "I am a foo!"
    }
  },
  {
    "*FooObject" : {
      "(TestCategory)categoryString" : "This is a NSString in category",
      "integer" : 1,
      "barObjectDate" : null,
      "string" : "I am a foo!"
    }
  },
  {
    "*FooObject" : {
      "(TestCategory)categoryString" : "This is a NSString in category",
      "integer" : 1,
      "barObjectDate" : null,
      "string" : "I am a foo!"
    }
  }
]
2017-02-06 16:03:40.387 HMCacheDemo[10959:1016121] dictionary is: 
{
  "1" : {
    "*FooObject" : {
      "(TestCategory)categoryString" : "This is a NSString in category",
      "integer" : 1,
      "barObjectDate" : null,
      "string" : "I am a foo!"
    }
  },
  "2" : {
    "*SubFooObject" : {
      "number" : 123,
      "**FooObject" : {
        "(TestCategory)categoryString" : "This is a NSString in category",
        "integer" : 1,
        "barObjectDate" : null,
        "string" : "I am a foo!"
      }
    }
  }
}

对象或数组以标准的 JSON 文本格式打印出来。JSON 中的对象表示 HMObject 子类实例时,将被标注为 "*" 以区分表示 NSDictionary 实例的对象。在一个 HMObject 子类 JSON 对象内部,其超类(也是一种 HMObject)将被两个 "*" 标注以区分属性。

此外,类别属性也会被打印,并以方法注册的类别名称作为前缀

+ (void)registerPropertyName:(NSString *)propertyName withCategoryName:(NSString *)categoryName.