coredata
简介
- Core Data 是一个模型层的技术
- Core Data 不仅是一个加载、保存数据的框架,它还能和内存中的数据很好的共事。
- 可用第三方库MDMCoreData
- 通过在应用程序启动时将参数-com.apple.CoreData.SQLDebug设置为1来让Core Data向我们展示它正在使用的底层SQL查询。
注:Core Data在管理包含许多实体、属性和关系的复杂对象图占有优势。
实现流程
-
创建xxx.xcdatamodeld文件。在创建工程的时候创建,或者File -> new -> file 里手动添加这个文件
Snip20180424_3.png -
创建数据实体,相当于数据库中的一张表
** Manual/None 告诉xcode不要生成相应文件
** Category / Extension告诉Xcode生成一个文件,ClassName + CoreDataGeneratedProperties。需要先buid生成对应的文件,然后才可以run
** Class DefinitionXcode生成两个文件,即上述文件,再加上ClassName + CoreDataClass。需要先buid生成对应的文件,然后才可以run
** 属性设置为Transient,则表示该属性不用持久化到磁盘;属性设置为Optional,则表示非必填字段(如果必填字段为空保存时会出错)
Snip20180507_1.png
- 通过Editor -> Create NSManagedObject Subclass 来生成数据模型文件
对象间相互关系
ManagedObjectModel、ManagedObjectContext、PersistentStoreCoordinator、NSManagedObject、NSPersistentContainer、NSEntityDescription
Snip20180424_1.png
- ManagedObjectModel需要通过数据模型文件来创建
// url 为CoreDataDemo.xcdatamodeld,注意扩展名为 momd,而不是 xcdatamodeld 类型
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataDemo" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
- 创建 PersistentStoreCoordinator 需要传入 managedObjectModel。sql数据库路径通过下面方法绑定
[storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:nil error:&error];
-
操作 CoreData 就可以通过 context 属性来完成,操作完之后调用 context 的 save 方法就可以数据持久化到本地。context需要绑定persistentStoreCoordinator。注意使用多个context以提高性能,context之间不同架构的理解。
Snip20180507_9.png -
NSManagedObject和ManagedObjectContext通过NSEntityDescription的方法关联起来。
+ (id)insertNewObjectForEntityForName:(NSString *)entityName
inManagedObjectContext:(NSManagedObjectContext *)context
-
NSPersistentContainer iOS10.0后可以使用。将Core Data堆栈封装在应用程序中的容器。NSPersistentContainer通过处理NSManagedObjectModel,NSPersistentStoreCoordinator和NSManagedObjectContext的创建,简化了核心数据堆栈的创建和管理。
Snip20180505_1.png
增
[NSEntityDescription insertNewObjectForEntityForName:@"TestEntity" inManagedObjectContext:_mainContext];
[_mainContext save:nil];
删
[_mainContext deleteObject:object];
或
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"TestEntity"];
//查询条件
NSPredicate *pre = [NSPredicate predicateWithFormat:@"uid = %@", @(3)];
request.predicate = pre;
NSBatchDeleteRequest *deleteRequest = [[NSBatchDeleteRequest alloc] initWithFetchRequest:request];
[_mainContext executeRequest:deleteRequest error:nil];
delete rules
Snip20180507_3.png
- Deny
如果关系目标(员工)中至少有一个对象,则不要删除源对象(部门)。
例如,如果您想要删除某个部门,则必须确保该部门中的所有员工都先转移到其他部门; 否则,该部门不能被删除。- Nullify(废止)
删除对象之间的关系,但不要删除任何对象。
这只有在员工的部门关系是可选的,或者确保在下一次保存操作之前为每个员工设置新部门时才有意义。- Cascade(级联)
删除源时,删除关系目的地处的对象。
例如,如果您删除某个部门,则同时启动该部门中的所有员工。- No Action(没有行动)
对关系目的地的对象不做任何事情。
例如,如果您删除了一个部门,即使他们仍然认为他们属于该部门,也会将所有员工保持原样。
改、查
查的时候配合NSExpression可以实现跟多效果,如查找最大最小值
NSFetchRequest
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"TestEntity"];
//查询条件
NSPredicate *pre = [NSPredicate predicateWithFormat:@"uid = %@", @(2)];
request.predicate = pre;
NSArray *resArray = [_mainContext executeFetchRequest:request error:nil];
TestEntity *entity = resArray.lastObject;
entity.name = @"lhm";
[_mainContext save:nil];
或者NSBatchUpdateRequest
注:使用NSBatchUpdateRequest [context hasChanges]为NO,且使用NSBatchUpdateRequest后,_mainContext进行fetch不能获取到更新后的数据,要_mainContext refresh后才可以将修改更新到_mainContext。且在使用之前要_mainContext save一下,要不可能回造成conflict
NSBatchUpdateRequest *request = [[NSBatchUpdateRequest alloc] initWithEntityName:@"TestEntity"];
request.predicate = [NSPredicate predicateWithFormat:@"uid == %@", @(3)];
request.propertiesToUpdate = @{ @"uid" :[NSExpression expressionForConstantValue:@(YES)]};
//request.propertiesToUpdate = @{
// @"uid" : @(10)
// };
request.resultType = NSUpdatedObjectsCountResultType;
NSBatchUpdateResult *res = (NSBatchUpdateResult *)[_mainContext executeRequest:request error:nil];
[[_mainContext refreshAllObjects]; //如果不刷新,获取值的时候可能会出错
异步查询NSAsynchronousFetchRequest
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"TestEntity"];
//查询条件
NSPredicate *pre = [NSPredicate predicateWithFormat:@"uid = %@", @(2)];
request.predicate = pre;
NSAsynchronousFetchRequest *requestAsy = [[NSAsynchronousFetchRequest alloc] initWithFetchRequest:request completionBlock:^(NSAsynchronousFetchResult * rsult) {
NSLog(@"%@",rsult);
}];
[_mainContext executeRequest:requestAsy error:nil];
可以增删改NSSaveChangesRequest
NSSaveChangesRequest *saveChangeRequest = [[NSSaveChangesRequest alloc] initWithInsertedObjects:<#(nullable NSSet<NSManagedObject *> *)#> updatedObjects:<#(nullable NSSet<NSManagedObject *> *)#> deletedObjects:<#(nullable NSSet<NSManagedObject *> *)#> lockedObjects:<#(nullable NSSet<NSManagedObject *> *)#>];;
[_mainContext executeRequest:saveChangeRequest error:nil];
取数据操作几种情况
- 对象已经在 context 中,这种操作基本上是没有任何代价的。
- 对象不在 context 中,但是因为你最近从 store 中取出过对象,所以持久化存储协调器缓存了对象的值。这个操作还算廉价(但是,一些操作会被锁住)。
- 当 context 和持久化存储协调器都是第一次访问这个对象,这种情况必须通过 store 从 SQLite 数据库取回。这种操作耗费最昂贵。
写入数据
- 因为保存操作好资源,所以不要频繁做保存操作,但也不要一下子将一大批更改交给 SQLite 处理
- 保存操作是原子性的,要么所有的更改会被提交给 store/SQLite 数据库,要么任何更改都不被保存。
- 由于Core Data 允许每个持久化存储协调器有多个 context,所以可能陷入持久化存储协调器层级的冲突之中。如:一个context要删除a数据,一个context要修改a数据
使用多线程的注意项
-
NSManagedObjectContext每个线程一个。
-
NSManagedObject实例不能在线程之间传递。
-
创建临时NSManagedObjectContext的速度非常快,因此您不必担心频繁创建和释放这些临时NSManagedObjectContext。关键是将persistentStoreCoordinator设置为我们在mainMOC上所拥有的一样,以便写作也可以在后台进行。
-
虽然NSPersistentStoreCoordinator也不是线程安全的,但NSManagedObjectContext知道如何在使用时正确锁定它。因此,我们可以根据需要将多个NSManagedObjectContext对象附加到单个NSPersistentStoreCoordinator,而不用担心发生冲突。
-
在某个线程的里保存数据的时候,这个线程对应的context调用save方法,继而需要保存的数据被推送到它的parentContext,parentContext如果还有parentContext,则会继续推送(parentContext调用save方法的时候修改不会自动推送到子context)。如果不设置为parentContext,则多个context之间的同步需要手动处理,详情:https://www.cocoanetics.com/2012/07/multi-context-coredata/ context和parentContext之间的关系图如下。所以context save之后parentContext也要save才能将修改保存到sql。注意,如果子context是NSPrivateQueueConcurrencyType类型,则
[subContext save:nil ]
后面不能直接跟[parentContext save:nil]
,以为此时子context是异步执行的,要使用
[subContext performBlockAndWait:^{
[subContext save:nil];
}];
[parentContext save:nil];
Snip20180508_2.png
处理大数据解决方案
- 更好的解决方案是将BLOB作为文件存储在磁盘上,并将它们作为持久存储中的路径引用。这可以保持持久存储的轻量级,并且不会不必要地强调核心数据。
- 主管理对象上下文在主线程上运行,仅用于与用户界面相关的任务。其他托管对象上下文使用主要托管对象上下文作为其父项,使用专用队列进行工作。
- 使用,存储之前如果要获取数据作比较,应该使用[NSPredicate predicateWithFormat:@"self.name IN {'1','2','4'}]
- 使用NSPersistentContainer的
- (void)performBackgroundTask:(void (^)(NSManagedObjectContext *))block;
- 分批次处理,并将批次包装在一个@autoreleasepool块中,并在退出autorelease块之前重置上下文。
性能优化注意点
- fetch的时候可以使用fetchBatchSize、fetchLimit、fetchOffset
- fetchBatchSize:每次获取的数量
- fetchLimit:总共获取的数量
- fetchOffset:分页获取的下标
-
request.returnsObjectsAsFaults = NO
的使用
如果没有这个设置,Core Data将把所有的值提取到持久存储协调器的行缓存中,但它不会填充实际的对象。通常这是有道理的,但如果要立即访问所有的对象,则应该将该属性设置为NO。 - 存储二进制文件的时候,使用
Allows External Storage
,如图片的存储。并且创建一个独立的实体用来存放二进制文件。
Snip20180524_2.png - 如果确定要用到relationships,则在fetch的时候应该设置取得对应的relationships
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Contact"];
[request setRelationshipKeyPathsForPrefetching:@[ @"photo" ]];
版本管理和轻量级数据迁移
轻量级数据迁移则为core data自动处理
更复杂的数据迁移参考地址:https://www.objc.io/issues/4-core-data/core-data-migration/
- 代码设置options
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
[storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error];
-
添加版本
Snip20180507_2.png -
设置当前版本
Snip20180507_5.png - 如果是增加或者删除数据的话,直接在新版本文件中操作即可,如果是修改,则要设置renaming ID
Snip20180507_7.png
备注:Hash Modifier是用来标注版本的,可以随意填写,当core data无法识别到版本更新的时候可以设置此值。Hash Modifier值不同表示版本不同。
其他语法使用
NSManagedObjectContext
• Turn a single managed object back into a fault:
mergeChanges如果是NO,则object的修改将被抛弃;如果是YES,则从缓存或内存中取的object值之后,其修改将被重新应用到新取得的object上。
[context refreshObject:object mergeChanges:NO];
• Reset an entire context, clearing all its managed objects:
[context reset];