核心数据多线程:代码示例

Tom*_*ing 3 multithreading core-data objective-c grand-central-dispatch nsmanagedobjectcontext

我的多线程Core Data启用应用程序一直存在问题,我想我应该仔细研究一下我在做什么以及如何做.如果以下情况有效,请告知我们.

我有一个DataManager处理Core Data东西的单例类.它有一个属性managedObjectContext,为每个线程返回不同的MOC.因此,给定NSMutableDictionary *_threadContextDict(字符串线程名称到上下文)和NSMutableDictionary *_threadDict(字符串线程名称到线程),它看起来像这样:

-(NSManagedObjectContext *)managedObjectContext
{
  if ([NSThread currentThread] == [NSThread mainThread])
  {
    MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
    return delegate.managedObjectContext; //MOC created in delegate code on main thread
  }
  else
  {
    NSString *thisThread = [[NSThread currentThread] description];
    {
      if ([_threadContextDict objectForKey:thisThread] != nil)
      {
        return [_threadContextDict objectForKey:thisThread];
      }
      else
      {
        NSManagedObjectContext *context = [[NSManagedObjectContext alloc]init];
        MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
        [context setPersistentStoreCoordinator:delegate.persistentStoreCoordinator];
        [_threadContextDict setObject:context forKey:thisThread];
        [_threadDict setObject:[NSThread currentThread] forKey:thisThread];

        //merge changes notifications
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
        [center addObserver:self selector:@selector(mergeChanges:)
            name:NSManagedObjectContextDidSaveNotification object:context];

        return context;
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

在该mergeChanges方法中,我将来自传入通知的更改合并到除生成通知的所有上下文之外的所有上下文.它看起来像这样:

-(void)mergeChanges:(NSNotification *)notification
{
  MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
  NSManagedObjectContext *context = delegate.managedObjectContext;
  [context performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification)
     withObject:notification waitUntilDone:YES];

  for (NSString *element in [_threadContextDict allKeys])
  {
    if (![element isEqualToString:[[NSThread currentThread] description]])
    {
      NSThread *thread = [_threadDict objectForKey:element];
      NSManagedObjectContext *threadContext = [_threadContextDict objectForKey:element];
      [threadContext performSelector:@selector(mergeChangesFromContextDidSaveNotification)
         onThread:thread withObject:notification waitUntilDone:YES];
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

每当我在MOC上保存更改时,都会调用saveContext此共享上的方法,该方法DataManager调用save从上述属性获取的上下文:

-(void)saveContext
{
  NSManagedObjectContext *context = self.managedObjectContext;
  NSError *err = nil;
  [context save:&err];
  //report error if necessary, etc.
}
Run Code Online (Sandbox Code Playgroud)

鉴于我对Core Data多线程规则的理解,我觉得这应该有效.我为每个线程使用单独的上下文,但是对于所有线程都使用相同的持久存储.但是当我使用它时,我会遇到很多合并冲突,即使我的线程没有处理相同的对象(NSManagedObject子类).我只是从网络下载数据,解析结果,并将它们保存到Core Data.

难道我做错了什么?我已经尝试使用NSLock实例来锁定某些东西,但后来我只是挂起了.

更新/解决方案:我能够通过添加一个简单的事情来完成这项工作:当我完成它时,从我的字典中删除线程/ MOC对的方法.在dispatch_async我每次调用Core Data数据的每个块的末尾,我调用[self removeThread],它从字典中删除当前线程及其MOC.我也只将更改合并到主线程MOC.实际上,这意味着每次我在后台线程上工作时,我都会得到一个全新的MOC.

我还通过添加一个数字来区分线程userInfoDict,而不是调用description.该数字是通过我的类上的readonly属性获得的,每次调用它时返回一个更高的数字.

J2t*_*heC 7

在充分尊重的情况下,您的方法是一场噩梦,如果出现问题,调试它以解决任何问题应该更糟糕.第一个问题是:

我有一个单独的DataManager

没有单个对象来管理不同线程上不同实体的核心数据操作.单身人士处理起来很棘手,特别是在多线程环境中,甚至是将其用于核心数据的更糟糕的方法.

第二件事,不要使用NSThread来处理多线程.有更多的现代API.使用Grand central dispatch或NSOperation/NSOperationQueue.自推出块(iOS 4)以来,Apple一直鼓励人们从NSThread迁移.并且为了将来参考,请不要按照您使用它的方式使用对象的描述.描述通常/主要用于调试目的.那里的信息不应该用来比较.甚至没有指针值(这就是为什么你应该使用isEqual而不是==).

这是您需要了解的核心数据和多线程:

  1. 每个线程创建一个上下文.核心数据模板已经为您创建了主线程上下文.在后台线程的执行开始时(在块内部,或在NSOperation子类的main方法上),初始化上下文.
  2. 初始化上下文并具有正确的persistentStoreCoordinator后,请监听NSManagedObjectContextObjectsDidChangeNotification.侦听通知的对象将在保存上下文的同一线程中接收通知.由于这与主线程不同,因此在使用接收上下文的线程上使用合并上下文进行合并调用.假设您正在使用与主线程不同的线程内的上下文,并且您想要与主线程合并,则需要在主线程内调用merge方法.你可以用dispatch_async(dispatch_get_main_queue(),^ {// code here})来做到这一点;
  3. 不要在其managedObjectContext所在的线程之外使用NSManagedObject.

使用这些和其他简单的规则,在多线程环境下管理核心数据更容易.您的方法更难实现,更糟糕的是调试.对您的架构进行一些更改.根据您使用的线程(而不是集中管理)管理上下文.不要在其范围之外保留对上下文的引用.创建第一个上下文后,在线程上创建上下文并不昂贵.您可以重用相同的上下文,只要它在同一个块/ NSOperation执行中.