AsyncCoreData 3.4.0

AsyncCoreData 3.4.0

Roen Rozpf维护。



  • 作者
  • 罗亮富

AsyncCoreData

  • 对 CoreData 数据库支持同步/异步操作
  • 确保同一数据在内存中的唯一性,节省内存空间,便于保持数据同步
  • 自带缓存,大大提高数据读取速度
  • 线程安全
  • 灵活的接口封装

待办事项

  • 自定义每个 Entity 的唯一索引字段 (NSPredicate 用 %K 格式化字段名称)

使用

假设有一个类 PlaceModel,要将它存储到数据库中

//PlaceModel.h
@interface PlaceModel : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *country;
@property (nonatomic, strong) NSString *zipCode;
@property (nonatomic) int level;
@end

配置

1 数据模型实现 UniqueValueProtocol 协议的方法

注意:虽然从 Xcode7 开始可以在 xcdatamodel 中设置 entity 的 constraints,但仍需遵守 UniqueValueProtocol 协议,调试发现即使约束相同,覆盖了原数据,NSManagedObjectContext 会有缓存,导致会有一条新的数据在缓存中。

//PlaceModel.m
-(id)uniqueValue {
    return self.zipCode; 
}

这个协议的目的在于约束对象的“唯一”值,有点类似 MySQL 的唯一索引,比如是用户信息的话,一般以 user_id 来代表用户的唯一性,那么就返回 user_id (即 return @self.user_id),如果某些对象不具备唯一性,那么就直接返回 nil,比如消息,就不具备唯一性,那就返回 nil

2 在 Xcode 中创建数据库模型文件

  • 通过 Xcode 创建一个名为“RRCDModel.xcdatamodeled”的数据库模型文件
  • 创建一个名为 PlaceEntity 的 Entity,设定相应字段(假设设定字段为 “uniqueID”、“name”、“country”、“level”)

注意

  • 每个 Entity 必须必须有一个 String 类型的 uniqueID 字段,这个字段就好比是 MySQL 数据库中的唯一索引,不同的是这个字段可以为 nil
  • 在 CoreData 数据库中,“uniqueID” 字段值将“自动”设定为模型中的 uniqueValue 值(即为何要遵守 UniqueValueProtocol 协议方法规范)
  • 在进行数据写入时,AsyncCoreData 将会自动检查数据模型的 uniqueValue 值(在不为 nil 的情况下)对应的记录在数据库中是否存在,若存在则更新该记录,否则则插入一条新记录
  • 为了加快查询速度,建议对 uniqueID 添加索引,添加方法为 1)选中 Entity,鼠标长按 Xcode 下面的“Add Entity”加号按钮,然后在下拉菜单中选中 Add Fetch Indexes

3 配置数据库存储位置及数据模型来源文件

数据库配置的方法全部定义在AsyncCoreData+Configration.h文件中

//这个是你数据库文件在本地的存储地址,可以灵活变换
NSURL *dataStoreUlr = [myDataDirectory URLByAppendingPathComponent:@"PrivateDataBase.sqlite"];

//@“RRCDModel”为你在Xcode中创建的数据库模型文件名称,后缀.xcdatamodeld忽略
[AsyncCoreData setPersistantStore:dataStoreUlr withModel:@"RRCDModel"];

有两个值得注意的牛逼之处:

  • +[AsyncCoreData setPersistantStore: withModel:]这个类方法的调用不受限制,可以通过这个方法来切换数据库。例如,在户外助手中,每个登录用户都有自己的独立数据库文件,这个时候,切换登录用户时,就需要通过它来切换数据库。
  • AsyncCoreData可以继承,子类和父类可以分别独立设置不同的数据库。例如,在户外助手中,有些数据是随着App的,比如轨迹、运动等,那么就可以保存在App共用数据库中;这种情况下,可以创建一个AppPublicCoreData的子类,然后单独对这个子类进行配置。

举一个例子:

//定义一个子类继承自 AsyncCoreData
@interface AppPublicCoreData : AsyncCoreData
@end
//公共数据库存储地址
NSURL *url = [publicDataDirectory URLByAppendingPathComponent:@"AppDataBase.sqlite"];

//@“PublicDataModel”为你在Xcode中创建的数据库模型文件名称,后缀.xcdatamodeld忽略
[AppPublicCoreData setPersistantStore:url withModel:@"PublicDataModel"];

4 设定数据库读写映射方法

//模型数据写入数据库配置方法
[AsyncCoreData setModelToDataBaseMapper:^(PlaceModel * model, NSManagedObject * _Nonnull managedObject) {
    // [managedObject setValue:model.uniqueValue forKey:@"uniqueID"]; //AsyncCoreData 自动设定
     [managedObject setValue:model.zipCode forKey:@"zip"];
     [managedObject setValue:model.uniqueValue forKey:@"name"];
     [managedObject setValue:model.country forKey:@"country"];
     [managedObject setValue:@(model.level) forKey:@"level"];

} forEntity:@"PlaceEntity"];

//从数据库数据获取数据模型方法
[AsyncCoreData setModelFromDataBaseMapper:^__kindof NSObject * _Nonnull(PlaceModel * _Nullable model, NSManagedObject * _Nonnull managedObject) {

    if(!model)//注意这里如果外部没有传入数据模型的话,要负责创建
        model = [PlaceModel new];
    // model.uniqueValue = [managedObject valueForKey:@"uniqueID"];//不需要
     model.zipCode = [managedObject valueForKey:@"zip"];
     model.name = [managedObject valueForKey:@"name"];
     model.country = [managedObject valueForKey:@"country"];
     model.level = [[managedObject valueForKey:@"level"] intValue];

    return model;

} forEntity:@"PlaceEntity"];

至此数据库的配置操作都已经完成

使用

提示:因为CoreData对数据库操作的接口都是通过NSManagedObjectContext来实现的,而NSManagedObjectContext是非线程安全的。

AsyncCoreData与业务相关的代码都定义在AsyncCoreData.h文件中。

AsyncCoreData针对多线程,针对每一种业务类型都设计了三种不同类型的方法。

//类型A 同步操作,最常规的数据,每次调用都会创建一个NSManagedObjectContext,相对来说效率并不是非常高
+[AsyncCoreData queryEntity:(NSString *)entityName xxx:];

//类型B 利用已有NSManagedObjectContext进行同步操作,这种方法的目的是解决类型A的效率问题,比如在一段循环代码中(这段代码都在同一个线程执行)需要频繁地进行数据库操作,这个时候就可以+[AsyncCoreData newContext]获取一个tempContext并引用住,然后每次调用的话将tempContext传给context
+[AsyncCoreData queryEntity:(NSString *)entityName xxx:... inContext:(NSManagedObjectContext *)context];

//类型C 带有xxxAsync:的方法,异步操作,这种方法的所有操作都是在“同一个‘后台线程进行, 操作结果通过block回调
+[AsyncCoreData queryEntity:(NSString *)entityName xxxAsync:... completion:^(xxx){
    //block
}];

注意:由于实际验证,类型B相对于类型A效果提升不明显,所以现在版本已经从接口中移除类型B方法。

使用宏定义简化代码

AsyncCoreData.h文件中有一个宏定义QUERY_ENTITY()

可以利用这个宏来简化我们的代码

举一个例子:

如果不使用宏定义,写起来是这样的

[AsyncCoreData queryEntity:@"PlaceEntity" saveModels:myDataModels];//保存数据

[AsyncCoreData queryEntity:@"PlaceEntity"  modelsWithPredicate:predicate inRange:range sortByKey:level reverse:YES];//查询数据

使用宏定义

//首先自己定义一个封装自 QUERY_ENTITY 的宏
#define DB_PLACE QUERY_ENTITY(@"PlaceEntity")
[DB_PLACE saveModels:myDataModels];//保存数据

[DB_PLACE modelsWithPredicate:predicate inRange:range sortByKey:level reverse:YES];//查询数据

安装

AsyncCoreData可以通过CocoaPods进行安装。要安装它,只需在你的Podfile中添加以下行

pod 'AsyncCoreData'

作者

罗亮富 [email protected]

许可证

AsyncCoreData遵循MIT许可证。更多信息请参阅LICENSE文件。