美文网首页iOSiOS之功能细节iOS之框架架构
CoreData 从入门到精通(四)并发操作

CoreData 从入门到精通(四)并发操作

作者: chaoyang805 | 来源:发表于2016-12-23 17:47 被阅读342次

    通常情况下,CoreData 的增删改查操作都在主线程上执行,那么对数据库的操作就会影响到 UI 操作,这在操作的数据量比较小的时候,执行的速度很快,我们也不会察觉到对 UI 的影响,但是当数据量特别大的时候,再把 CoreData 的操作放到主线程中就会影响到 UI 的流畅性。自然而然地我们就会想到使用后台线程来处理大量的数据操作。

    使用后台 managedObjectContext

    CoreData 里使用后台更新数据最常用的方案是一个 persistentStoreCoordinator 持久化存储协调器对应两个 managedObjectContext 管理上下文,NSManagedObjectContext 在创建时,可以传入 ConcurrencyType 来指定 context 的并发类型。指定 NSMainQueueConcurrencyType 就是我们平时创建的运行在主队列的 context;指定成 NSPrivateQueueConcurrencyType 的话,context 就会运行在它所管理的一个私有队列中;另外还有 NSConfinementConcurrencyType 是适用于旧设备的并发类型,现在已经被废弃了,所以实际上只有两种并发类型。
    下面是创建 backgroundContext 的代码:

    NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    backgroundContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
    

    在最新的 iOS 10 中,CoreData 栈的创建被封装在了 NSPersistentContainer 类中,用它来创建 backgroundContext 更加简单:

    NSManagedObjectContext *backgroundContext = ((AppDelegate *)[UIApplication sharedApplication].delegate).persistentContainer.newBackgroundContext;
    

    另外,后台 context 的操作得放在 performBlockperformBlockAndWait 方法里执行,performBlock 会异步的执行,不会阻塞当前的线程,而 performBlockAndWait 则会阻塞当前的线程直到方法返回才会继续向下执行。下面是一段后台插入数据的示例代码:

    [self.backgroundContext performBlock:^{
    
        for (NSUInteger i = 0; i < 100000; i++) {
                    
            NSString *name = [NSString stringWithFormat:@"student-%d", arc4random_uniform(9999)];
          int16_t age = arc4random_uniform(10) + 10;
          int16_t stuId = arc4random_uniform(9999);
          Student *student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.backgroundContext];
          student.studentName = name;
          student.studentAge = age;
          student.studentId = stuId;
          
        }
        NSError *error;
        [self.backgroundContext save:&error];
        [self.logger dealWithError:error
                          whenFail:@"failed to insert"
                       whenSuccess:@"insert success"];
            
    }];
    

    后台插入数据之后,还没有完,因为数据是通过后台的 context 写入到本地的持久化数据库的,所以这时候主队列的 context 是不知道本地数据变化的,所以还需要通知到主队列的 context:“数据库的内容有变化啦,看看你有没有需要合并的”。这个过程可以通过监听一条通知来实现。这个通知就是 NSManagedObjectContextDidSaveNotification,在每次调用 NSManagedObjectContextsave:方法时都会自动发送,通知中的 userInfo 中包含了修改的数据,可以通过 NSInsertedObjectsKeyNSUpdatedObjectsKeyNSDeletedObjectsKey 这三个 key 获取到。

    NSManagedObjectContextDidSaveNotification-w600

    收到通知之后,只需要调用 [self.mainContext mergeChangesFromContextDidSaveNotification:note] 就可以将修改的数据合并到主线程的 context

    下面是示例代码:

    - (void)viewDidLoad {
        [super viewDidLoad];
     
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveContextSave:) name:NSManagedObjectContextDidSaveNotification object:self.backgroundContext];
    }
    
    - (void)doSometingInsertingInBackground {
        // backgroundContext ....
    }
    
    - (void)receiveContextSave:(NSNotification *)note {
        [self.context mergeChangesFromContextDidSaveNotification:note];
    }
    
    - (void)dealloc {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    
    

    注意:通知的 userInfo 里保存的 managedObjects 不可以直接在另一个线程的 context 中直接使用!也就是 managedObject 不是跨线程的,如果想要在别的线程操作,必须通过 objectId 在另一个 context 里再重新获得这个 object

    - (void)receiveContextSave:(NSNotification *)note {
        
        [self.context mergeChangesFromContextDidSaveNotification:note];
        
        NSSet<Student *> *managedObjects = note.userInfo[NSInsertedObjectsKey];
        NSManagedObjectID *studentId = managedObjects.allObjects[0].objectID;
        
        [self.context performBlock:^{
            
            // 这是错的
            // Student *wrongStudent = managedObjects.allObjects[0];
            
            // 应该这么做
            Student *student = [self.context objectWithID:studentId];
            // modify student...
        }];
    }
    

    相关文章

      网友评论

      • 请叫我喵_喵:上面两个问题烦死你解答,谢谢!
      • 请叫我喵_喵:NSManagedObjectID *studentId = managedObjects.allObjects[0].objectID;

        额这个NSManagedObjectID是每个继承NSManagedObject的对象都有的内含属性么?
      • 请叫我喵_喵:问:[self.backgroundContext performBlock:^{里面是save方法为什么不在for循环里调用,for循环里创建的student不是局部变量么,您在for循环在调用save方法什么都保存不到吧?

      本文标题:CoreData 从入门到精通(四)并发操作

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