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