认识CoreData - 多线程

作者: 刘小壮 | 来源:发表于2016-07-22 19:17 被阅读9330次
    该文章属于<简书 — 刘小壮>原创,转载请注明:

    <简书 — 刘小壮> http://www.jianshu.com/p/283e67ba12a3


    CoreData使用相关的技术点已经讲差不多了,我所掌握的也就这么多了....

    在本篇文章中主要讲CoreData的多线程,其中会包括并发队列类型、线程安全等技术点。我对多线程的理解可能不是太透彻,文章中出现的问题还请各位指出。在之后公司项目使用CoreData的过程中,我会将其中遇到的多线程相关的问题更新到文章中。

    在文章的最后,会根据我对CoreData多线程的学习,以及在工作中的具体使用,给出一些关于多线程结构的设计建议,各位可以当做参考。

    文章中如有疏漏或错误,还请各位及时提出,谢谢!😊


    占位图

    MOC并发队列类型

    CoreDataMOC是支持多线程的,可以在创建MOC对象时,指定其并发队列的类型。当指定队列类型后,系统会将操作都放在指定的队列中执行,如果指定的是私有队列,系统会创建一个新的队列。但这都是系统内部的行为,我们并不能获取这个队列,队列由系统所拥有,并由系统将任务派发到这个队列中执行的

    NSManagedObjectContext并发队列类型:
    • NSConfinementConcurrencyType : 如果使用init方法初始化上下文,默认就是这个并发类型。这个枚举值是不支持多线程的,从名字上也体现出来了。

    • NSPrivateQueueConcurrencyType : 私有并发队列类型,操作都是在子线程中完成的。

    • NSMainQueueConcurrencyType : 主并发队列类型,如果涉及到UI相关的操作,应该考虑使用这个枚举值初始化上下文。

    其中NSConfinementConcurrencyType类型在iOS9之后已经被苹果废弃,不建议使用这个API。使用此类型创建的MOC,调用某些比较新的CoreDataAPI可能会导致崩溃。

    MOC多线程调用方式

    CoreDataMOC不是线程安全的,在多线程情况下使用MOC时,不能简单的将MOC从一个线程中传递到另一个线程中使用,这并不是CoreData的多线程,而且会出问题。对于MOC多线程的使用,苹果给出了自己的解决方案。

    在创建的MOC中使用多线程,无论是私有队列还是主队列,都应该采用下面两种多线程的使用方式,而不是自己手动创建线程。调用下面方法后,系统内部会将任务派发到不同的队列中执行。可以在不同的线程中调用MOC的这两个方法,这个是允许的。

    - (void)performBlock:(void (^)())block            异步执行的block,调用之后会立刻返回。
    - (void)performBlockAndWait:(void (^)())block     同步执行的block,调用之后会等待这个任务完成,才会继续向下执行。
    

    下面是多线程调用的示例代码,在多线程的环境下执行MOCsave方法,就是将save方法放在MOCblock体中异步执行,其他方法的调用也是一样的。

    [context performBlock:^{
        [context save:nil];
    }];
    

    但是需要注意的是,这两个block方法不能在NSConfinementConcurrencyType类型的MOC下调用,这个类型的MOC是不支持多线程的,只支持其他两种并发方式的MOC

    多线程的使用

    在业务比较复杂的情况下,需要进行大量数据处理,并且还需要涉及到UI的操作。对于这种复杂需求,如果都放在主队列中,对性能和界面流畅度都会有很大的影响,导致用户体验非常差,降低屏幕FPS。对于这种情况,可以采取多个MOC配合的方式。

    CoreData多线程的发展中,在iOS5经历了一次比较大的变化,之后可以更方便的使用多线程。从iOS5开始,支持设置MOCparentContext属性,通过这个属性可以设置MOC父MOC。下面会针对iOS5之前和之后,分别讲解CoreData的多线程使用。

    尽管现在的开发中早就不兼容iOS5之前的系统了,但是作为了解这里还是要讲一下,而且这种同步方式在iOS5之后也是可以正常使用的,也有很多人还在使用这种同步方式,下面其他章节也是同理。

    iOS5之前使用多个MOC

    iOS5之前实现MOC的多线程,可以创建多个MOC,多个MOC使用同一个PSC,并让多个MOC实现数据同步。通过这种方式不用担心PSC在调用过程中的线程问题,MOC在使用PSC进行save操作时,会对PSC进行加锁,等当前加锁的MOC执行完操作之后,其他MOC才能继续执行操作。

    每一个PSC都对应着一个持久化存储区,PSC知道存储区中数据存储的数据结构,而MOC需要使用这个PSC进行save操作的实现。

    多线程结构

    这样做有一个问题,当一个MOC发生改变并持久化到本地时,系统并不会将其他MOC缓存在内存中的NSManagedObject对象改变。所以这就需要我们在MOC发生改变时,将其他MOC数据更新。

    根据上面的解释,在下面例子中创建了一个主队列的mainMOC,主要用于UI操作。一个私有队列的backgroundMOC,用于除UI之外的耗时操作,两个MOC使用的同一个PSC

    // 获取PSC实例对象
    - (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    
        // 创建托管对象模型,并指明加载Company模型文件
        NSURL *modelPath = [[NSBundle mainBundle] URLForResource:@"Company" withExtension:@"momd"];
        NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelPath];
    
        // 创建PSC对象,并将托管对象模型当做参数传入,其他MOC都是用这一个PSC。
        NSPersistentStoreCoordinator *PSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    
        // 根据指定的路径,创建并关联本地数据库
        NSString *dataPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
        dataPath = [dataPath stringByAppendingFormat:@"/%@.sqlite", @"Company"];
        [PSC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:dataPath] options:nil error:nil];
    
        return PSC;
    }
    
    // 初始化用于本地存储的所有MOC
    - (void)createManagedObjectContext {
    
        // 创建PSC实例对象,其他MOC都用这一个PSC。
        NSPersistentStoreCoordinator *PSC = self.persistentStoreCoordinator;
    
        // 创建主队列MOC,用于执行UI操作
        NSManagedObjectContext *mainMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        mainMOC.persistentStoreCoordinator = PSC;
    
        // 创建私有队列MOC,用于执行其他耗时操作
        NSManagedObjectContext *backgroundMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        backgroundMOC.persistentStoreCoordinator = PSC;
    
        // 通过监听NSManagedObjectContextDidSaveNotification通知,来获取所有MOC的改变消息
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
    }
    
    // MOC改变后的通知回调
    - (void)contextChanged:(NSNotification *)noti {
        NSManagedObjectContext *MOC = noti.object;
        // 这里需要做判断操作,判断当前改变的MOC是否我们将要做同步的MOC,如果就是当前MOC自己做的改变,那就不需要再同步自己了。
        // 由于项目中可能存在多个PSC,所以下面还需要判断PSC是否当前操作的PSC,如果不是当前PSC则不需要同步,不要去同步其他本地存储的数据。
        [MOC performBlock:^{
            // 直接调用系统提供的同步API,系统内部会完成同步的实现细节。
            [MOC mergeChangesFromContextDidSaveNotification:noti];
        }];
    }
    

    在上面的Demo中,创建了一个PSC,并将其他MOC都关联到这个PSC上,这样所有的MOC执行本地持久化相关的操作时,都是通过同一个PSC进行操作的。并在下面添加了一个通知,这个通知是监听所有MOC执行save操作后的通知,并在通知的回调方法中进行数据的合并

    iOS5之后使用多个MOC

    iOS5之后,MOC可以设置parentContext,一个parentContext可以拥有多个ChildContext。在ChildContext执行save操作后,会将操作pushparentContext,由parentContext去完成真正的save操作,而ChildContext所有的改变都会被parentContext所知晓,这解决了之前MOC手动同步数据的问题。

    需要注意的是,在ChildContext调用save方法之后,此时并没有将数据写入存储区,还需要调用parentContextsave方法。因为ChildContext并不拥有PSCChildContext也不需要设置PSC,所以需要parentContext调用PSC来执行真正的save操作。也就是只有拥有PSCMOC执行save操作后,才是真正的执行了写入存储区的操作。

    - (void)createManagedObjectContext {
        // 创建PSC实例对象,还是用上面Demo的实例化代码
        NSPersistentStoreCoordinator *PSC = self.persistentStoreCoordinator;
    
        // 创建主队列MOC,用于执行UI操作
        NSManagedObjectContext *mainMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        mainMOC.persistentStoreCoordinator = PSC;
    
        // 创建私有队列MOC,用于执行其他耗时操作,backgroundMOC并不需要设置PSC
        NSManagedObjectContext *backgroundMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        backgroundMOC.parentContext = mainMOC;
    
        // 私有队列的MOC和主队列的MOC,在执行save操作时,都应该调用performBlock:方法,在自己的队列中执行save操作。
        // 私有队列的MOC执行完自己的save操作后,还调用了主队列MOC的save方法,来完成真正的持久化操作,否则不能持久化到本地
        [backgroundMOC performBlock:^{
            [backgroundMOC save:nil];
        
            [mainMOC performBlock:^{
                [mainMOC save:nil];
            }];
        }];
    }
    

    上面例子中创建一个主队列的mainMOC,来完成UI相关的操作。创建私有队列的backgroundMOC,处理复杂逻辑以及数据处理操作,在实际开发中可以根据需求创建多个backgroundMOC。需要注意的是,在backgroundMOC执行完save方法后,又在mainMOC中执行了一次save方法,这步是很重要的。

    iOS5之前进行数据同步

    就像上面章节中讲到的,在iOS5之前存在多个MOC的情况下,一个MOC发生更改并提交存储区后,其他MOC并不知道这个改变,其他MOC和本地存储的数据是不同步的,所以就涉及到数据同步的问题。

    进行数据同步时,会遇到多种复杂情况。例如只有一个MOC数据发生了改变,其他MOC更新时并没有对相同的数据做改变,这样不会造成冲突,可以直接将其他MOC更新。

    如果在一个MOC数据发生改变后,其他MOC对相同的数据做了改变,而且改变的结果不同,这样在同步时就会造成冲突。下面将会按照这两种情况,分别讲一下不同情况下的冲突处理方式。

    简单情况下的数据同步

    简单情况下的数据同步,是针对于只有一个MOC的数据发生改变,并提交存储区后,其他MOC更新时并没有对相同的数据做改变,只是单纯的同步数据的情况。

    NSManagedObjectContext类中,根据不同操作定义了一些通知。在一个MOC发生改变时,其他地方可以通过MOC中定义的通知名,来获取MOC发生的改变。在NSManagedObjectContext中定义了下面三个通知:

    • NSManagedObjectContextWillSaveNotification MOC将要向存储区存储数据时,调用这个通知。在这个通知中不能获取发生改变相关的NSManagedObject对象。

    • NSManagedObjectContextDidSaveNotification MOC向存储区存储数据后,调用这个通知。在这个通知中可以获取改变、添加、删除等信息,以及相关联的NSManagedObject对象。

    • NSManagedObjectContextObjectsDidChangeNotificationMOC中任何一个托管对象发生改变时,调用这个通知。例如修改托管对象的属性。

    通过监听NSManagedObjectContextDidSaveNotification通知,获取所有MOCsave操作。

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(settingsContext:) name:NSManagedObjectContextDidSaveNotification object:nil];
    

    不需要在通知的回调方法中,编写代码对比被修改的托管对象。MOC为我们提供了下面的方法,只需要将通知对象传入,系统会自动同步数据

    - (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification;
    

    下面是通知中的实现代码,但是需要注意的是,由于通知是同步执行的,在通知对应的回调方法中所处的线程,和发出通知的MOC执行操作时所处的线程是同一个线程,也就是系统performBlock:回调方法分配的线程。

    所以其他MOC在通知回调方法中,需要注意使用performBlock:方法,并在block体中执行操作。

    - (void)settingsContext:(NSNotification *)noti {
        [context performBlock:^{
            // 调用需要同步的MOC对象的merge方法,直接将通知对象当做参数传进去即可,系统会完成同步操作。
            [context mergeChangesFromContextDidSaveNotification:noti];
        }];
    }
    

    复杂情况下的数据同步

    在一个MOC对本地存储区的数据发生改变,而其他MOC也对同样的数据做了改变,这样后面执行save操作的MOC就会冲突,并导致后面的save操作失败,这就是复杂情况下的数据合并。

    这是因为每次一个MOC执行一次fetch操作后,会保存一个本地持久化存储的状态,当下次执行save操作时会对比这个状态和本地持久化状态是否一样。如果一样,则代表本地没有其他MOC对存储发生过改变;如果不一样,则代表本地持久化存储被其他MOC改变过,这就是造成冲突的根本原因。

    对于这种冲突的情况,可以通过MOC对象指定解决冲突的方案,通过mergePolicy属性来设置方案。mergePolicy属性有下面几种可选的策略,默认是NSErrorMergePolicy方式,这也是唯一一个有NSError返回值的选项。

    • NSErrorMergePolicy : 默认值,当出现合并冲突时,返回一个NSError对象来描述错误,而MOC和持久化存储区不发生改变

    • NSMergeByPropertyStoreTrumpMergePolicy : 以本地存储为准,使用本地存储来覆盖冲突部分。

    • NSMergeByPropertyObjectTrumpMergePolicy : 以MOC的为准,使用MOC来覆盖本地存储的冲突部分。

    • NSOverwriteMergePolicy : 以MOC为准,用MOC的所有NSManagedObject对象覆盖本地存储的对应对象。

    • NSRollbackMergePolicy : 以本地存储为准,MOC所有的NSManagedObject对象被本地存储的对应对象所覆盖。

    上面五种策略中,除了第一个NSErrorMergePolicy的策略,其他四种中NSMergeByPropertyStoreTrumpMergePolicyNSRollbackMergePolicy,以及NSMergeByPropertyObjectTrumpMergePolicyNSOverwriteMergePolicy看起来是重复的。

    其实它们并不是冲突的,这四种策略的不同体现在,对没有发生冲突的部分应该怎么处理NSMergeByPropertyStoreTrumpMergePolicyNSMergeByPropertyObjectTrumpMergePolicy对没有冲突的部分,未冲突部分数据并不会受到影响。而NSRollbackMergePolicyNSOverwriteMergePolicy则是无论是否冲突,直接全部替换。

    题外话:
    对于MOC的这种合并策略来看,有木有感觉到CoreData解决冲突的方式,和SVN解决冲突的方式特别像。。。

    线程安全

    无论是MOC还是托管对象,都不应该在其他MOC的线程中执行操作,这两个API都不是线程安全的。但MOC可以在其他MOC线程中调用performBlock:方法,切换到自己的线程执行操作。

    如果其他MOC想要拿到托管对象,并在自己的队列中使用托管对象,这是不允许的,托管对象是不能直接传递到其他MOC的线程的。但是可以通过获取NSManagedObjectNSManagedObjectID对象,在其他MOC中通过NSManagedObjectID对象,从持久化存储区中获取NSManagedObject对象,这样就是允许的。NSManagedObjectID是线程安全,并且可以跨线程使用的。

    可以通过MOC获取NSManagedObjectID对应的NSManagedObject对象,例如下面几个MOCAPI

    NSManagedObject *object = [context objectRegisteredForID:objectID];
    NSManagedObject *object = [context objectWithID:objectID];
    

    通过NSManagedObject对象的objectID属性,获取NSManagedObjectID类型的objectID对象。

    NSManagedObjectID *objectID = object.objectID;
    

    CoreData多线程结构设计

    上面章节中写的大多都是怎么用CoreData多线程,在掌握多线程的使用后,就可以根据公司业务需求,设计一套CoreData多线程结构了。对于多线程结构的设计,应该本着尽量减少主线程压力的角度去设计,将所有耗时操作都放在子线程中执行。

    对于具体的设计我根据不同的业务需求,给出两种设计方案的建议。

    两层设计方案

    在项目中多线程操作比较简单时,可以创建一个主队列mainMOC,和一个或多个私有队列的backgroundMOC。将所有backgroundMOCparentContext设置为mainMOC,采取这样的两层设计一般就能够满足大多数需求了。

    两层设计方案

    将耗时操作都放在backgroundMOC中执行,mainMOC负责所有和UI相关的操作。所有和UI无关的工作都交给backgroundMOC,在backgroundMOC对数据发生改变后,调用save方法会将改变pushmainMOC中,再由mainMOC执行save方法将改变保存到存储区。

    代码这里就不写了,和上面例子中设置parentContext代码一样,主要讲一下设计思路。

    三层设计方案

    但是我们发现,上面的save操作最后还是由mainMOC去执行的,backgroundMOC只是负责处理数据。虽然mainMOC只执行save操作并不会很耗时,但是如果save涉及的数据比较多,这样还是会对性能造成影响的。

    虽然客户端很少涉及到大量数据处理的需求,但是假设有这样的需求。可以考虑在两层结构之上,给mainMOC之上再添加一个parentMOC,这个parentMOC也是私有队列的MOC,用于处理save操作。

    三层设计方案

    这样CoreData存储的结构就是三层了,最底层是backgroundMOC负责处理数据,中间层是mainMOC负责UI相关操作,最上层也是一个backgroundMOC负责执行save操作。这样就将影响UI的所有耗时操作全都剥离到私有队列中执行,使性能达到了很好的优化。

    需要注意的是,执行MOC相关操作时,不要阻塞当前主线程。所有MOC的操作应该是异步的,无论是子线程还是主线程,尽量少的使用同步block方法。

    MOC同步时机

    设置MOCparentContext属性之后,parent对于child的改变是知道的,但是child对于parent的改变是不知道的。苹果这样设计,应该是为了更好的数据同步。

    Employee *emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:backgroundMOC];
    emp.name = @"lxz";
    emp.brithday = [NSDate date];
    emp.height = @1.7f;
    
    [backgroundMOC performBlock:^{
        [backgroundMOC save:nil];
        [mainMOC performBlock:^{
            [mainMOC save:nil];
        }];
    }];
    

    在上面这段代码中,mainMOCbackgroundMOCparentContext。在backgroundMOC执行save方法前,backgroundMOCmainMOC都不能获取到Employee的数据,在backgroundMOC执行完save方法后,自身上下文发生改变的同时,也将改变pushmainMOC中,mainMOC也具有了Employee对象。

    所以在backgroundMOCsave方法执行时,是对内存中的上下文做了改变,当拥有PSCmainMOC执行save方法后,是对本地存储区做了改变。


    好多同学都问我有Demo没有,其实文章中贴出的代码组合起来就是个Demo。后来想了想,还是给本系列文章配了一个简单的Demo,方便大家运行调试,后续会给所有博客的文章都加上Demo

    Demo只是来辅助读者更好的理解文章中的内容,应该博客结合Demo一起学习,只看Demo还是不能理解更深层的原理Demo中几乎每一行代码都会有注释,各位可以打断点跟着Demo执行流程走一遍,看看各个阶段变量的值。

    Demo地址刘小壮的Github


    这两天更新了一下文章,将CoreData系列的六篇文章整合在一起,做了一个PDF版的《CoreData Book》,放在我Github上了。PDF上有文章目录,方便阅读。

    如果你觉得不错,请把PDF帮忙转到其他群里,或者你的朋友,让更多的人了解CoreData,衷心感谢!😁

    相关文章

      网友评论

      • LJ_影子:楼主请问下。malloc: *** error for object 0x101a51000: incorrect checksum for freed object - object was probably modified after being freed. 进行save操作时报错。 有遇到过吗?
        LJ_影子:内存导致的问题? 这个是偶尔事件。有时候又不会报错
        刘小壮:检查一下内存管理,是内存导致的问题。
        刘小壮:你save的时候,模型对象释放了吧
      • WXHWang_489a:if (!dataArray) {
        return;
        }
        NSManagedObjectContext *context = [self managedObjectContext];
        for (NSString *aid in dataArray) {

        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"aid = %@",aid];
        //首先你需要建立一个request
        NSFetchRequest * request = [[NSFetchRequest alloc] init];
        [request setEntity:[NSEntityDescription entityForName:FacegroupMessage inManagedObjectContext:context]];
        [request setPredicate:predicate];//这里相当于sqlite中的查询条件,具体格式参考苹果文档


        //https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Predicates/Articles/pCreating.html
        NSError *error = nil;
        NSArray *result = [context executeFetchRequest:request error:&error];//这里获取到的是一个数组,你需要取出你要更新的那个obj
        for (YKFacegroupMessage *faceinfo in result)
        {

        if (face_uid) {
        faceinfo.face_uid = face_uid;
        }
        if (face_name) {
        [faceinfo setValue:face_name forKey:@"face_name"];
        }
        }

        //保存
        if ([context save:&error]) {
        //更新成功
        NSLog(@"单个数据face_name更新成功");
        }
        }
        楼主看下这段代码 这边保存成功 但是我在另个界面读取这个face_name 的值时未null 这是什么原因?又遇到这样的问题吗
      • 8fe8946fa366:想请问作者,iOS5之后的数据同步是什么样的呢
        刘小壮:iOS5之后通过parentContext进行数据同步,文章中写了的。
      • lenolee:不看不知道,原来有78页,辛苦,果断star
        刘小壮:求转发啊:pray:
        刘小壮:是的,搞了好久,谢谢
      • a609535e76bd:请教一下:三层设计方案中backgroundMOC save 操作之后是否也要对mainMOC 进行save操作呢?谢谢
      • 939aa01ddf09:非常感谢博主的分享,让我对CoreData的使用方便了很多。
        关于CoreData三层设计的方案,根据你的结构图我写下了如下的代码:
        NSPersistentStoreCoordinator *PSC = [self bme_PSCWithModelName:name
        isMigrationVersion:isMigrationVersion];

        //创建最上层私有队列MOC,用于执行save操作
        NSManagedObjectContext *topPrivateMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        topPrivateMOC.persistentStoreCoordinator = PSC;

        //创建中间层主队列MOC,用于执行UI操作(不需要设置PSC)
        NSManagedObjectContext *mainMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        mainMOC.parentContext = topPrivateMOC;

        NSManagedObjectContext *thirdPrivateMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        thirdPrivateMOC.parentContext = mainMOC;

        //私有队列的MOC和主队列的MOC,在执行save操作时,都应该调用performBlock:方法,在自己队列中执行save操作。
        //私有队列的MOC执行完自己的save操作后,还调用了主队列的MOC的save方法,来完成真正的持久化操作,否则不能持久化到本地
        [thirdPrivateMOC performBlock:^{
        [thirdPrivateMOC save:nil];

        //privateMOC执行完save方法后,又在mainMOC中执行了一次save方法,这步是很重要的。
        [topPrivateMOC performBlock:^{
        [topPrivateMOC save:nil];
        }];
        }];

        //主队列MOC执行save操作刷新UI,真正的持久化操作交给私有队列topPrivateMOC来完成。
        [mainMOC performBlock:^{
        [mainMOC save:nil];

        [topPrivateMOC performBlock:^{
        [topPrivateMOC save:nil];
        }];
        }];
        我的疑问是当第三层私有thirdPrivateMOC,调用save之后,是直接调用最顶层的topPrivateMOC进行持久化保存还是先调用中间层mainMOC,然后通过mainMOC再去调用顶层的topPrivateMOC呢?
      • Finy:非常感谢作者的分享,给我的帮助很大。有个疑问:对于parent和child的两层结构设计中,读取数据时也是通过child来操作,因为child是NSPrivateQueueConcurrencyType类型,意味着child的读取行为是异步的吗?如果读取数据比较多时是不是需要通过通知等方式告知读取完毕呢?因为如果是我想象中的这样意味着正常的程序流需要更改,谢谢。
        刘小壮:@Finy 哦,明白了。如果执行executeFetchRequest:方法进行查询操作,是同步查找的,直接在下面执行其他操作即可。
        Finy:@刘小壮 可能我的问题没有描述清楚,我的意思是假如要读取数据库中比较多的数据时,而非更新删除插入操作。比如,在正常的程序流中调用读取数据的函数,该函数返回读取完毕后的数据,读取时用child context来操作,这个读取数据的函数是否需要通过通知或闭包来异步告知调用者数据已经读取完毕,可以使用数据了。还是只要正常的程序流调用读取数据函数后马上使用函数返回的数据即可?谢谢!
        刘小壮:用了parentContext属性的方式,就不用通知做同步操作了。
      • KavinZhou:真心感谢!研读你的几篇文章收获很大,真心再次感谢!!
        刘小壮:谢谢你的鼓励,我也一直在努力让自己变得更强。:blush:
      • _没心没肺能活百岁_:楼主 我有几个问题:
        1.关于ios5之前的moc执行save之前发送NSManagedObjectContextDidSaveNotification问题:假设有moc1与moc2两个,moc1首先对某一数据做了修改,但此时执行并未save到本地数据库中,接着moc2也对此数据做了修改并且还未save。此时,若moc1进行save操作,会发出NSManagedObjectContextDidSaveNotification通知,当moc2接收到通知时会执行mergeChangesFromContextDidSaveNotification操作,那此时由于moc2中也对此数据进行了修改,所以在执行merge时会不会出现冲突,并且最后moc2中保存的是哪个状态呢?

        2.若moc1与moc2并未监听NSManagedObjectContextDidSaveNotification,moc1对数据进行了修改,并save至本地数据库,随后moc2也对此数据进行了修改,按照理论在moc2进行save操作的时候会出现冲突,并会根据mergepolicy进行冲突解决,意思就是在moc1执行save操作,moc2未执行save操作的过程中,moc2与本地数据库中的数据是不同步的,这样会不会出现什么问题呢?

        3.对于ios5之后的同步,一个parent有好几个child,当某一个child发生变化时,parent均会知道,所以在执行save时不会出现冲突,但是,parent执行save之后,对于其他的child并不能感知,也就是其他的child在执行save之前的数据与数据库中的数据是不同步的,这样会出现问题,一般要怎样解决这种同步问题呢?

        问题比较小白,还请楼主见谅
        :smiley: :smile: :smile:
        小生图图:楼主iOS5之后每个MOC执行performBlock操作save时都要执行一次parentContext的save操作吗
        小生图图:楼主demo里面怎么没有解决同步的代码啊
        刘小壮:1和2. 会出现冲突,文章中介绍了冲突处理的几种方式。就类似SVN的冲突处理一样,可以选择性忽略。
        3. parent一般不会直接进行增删改查操作,都是由Child来进行的。
      • hithere:清晰明了,看了好多文章,这篇最好
        刘小壮:@hithere 得到认可,好开心呐 :smile:
      • 阿浩在西边:parent和child的模式其实挺好的,就是当一种情况,parent有多个children的时候,数据同步比较麻烦,特别是update,insert的数据因为child里的策略,如果child里找不到,会到parent里面找所以不用担心,但比如delete和update的,children之间不同步就会出现很多问题。
        阿浩在西边:@刘小壮 恩,我们里面也是。Magical Record里面的import都会建一个临时的context,用完就销毁。以前碰到一个问题,就是CoreData没有提供类似主键的功能,然后我们是只能自己过滤的,如果主键已经存在了,就不导入了,而是覆盖。但Magical Record的那个机制就是会有机会几个context在同时保存,导致这个主键判断不准确,然后产生了重复主键的数据,后来挖了很久才挖出来。不知道你们有没有碰到过这样的问题。
        刘小壮:@阿浩在西边 我们项目尽量少建childMOC,还能少做点同步。
        刘小壮:@阿浩在西边 多个MOC需要注意同步的问题,同步处理起来挺麻烦的,用parent感觉还舒服点。

      本文标题:认识CoreData - 多线程

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