coredata

作者: liboxiang | 来源:发表于2018-04-24 23:57 被阅读41次

简介

  • 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
Snip20180424_2.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];

参考链接

https://www.objccn.io/issue-4-1/

相关文章

网友评论

      本文标题:coredata

      本文链接:https://www.haomeiwen.com/subject/bjgslftx.html