【问题标题】:iOS: How do I synchronize my managedObjectContext when using Grand Central Dispatch (GCD)?iOS:使用 Grand Central Dispatch (GCD) 时如何同步我的 managedObjectContext?
【发布时间】:2012-08-10 22:58:43
【问题描述】:

我正在使用以下代码来运行解析我的 xml 文件的函数...

dispatch_queue_t queue = dispatch_queue_create("updateQueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue,^ { [self updateFromXMLFile:@"http://path/to/file.xml"]; } ); 
dispatch_async(queue,^ { [self updateFromXMLFile:@"http://path/to/file1.xml"]; } );
dispatch_async(queue,^ { [self updateFromXMLFile:@"http://path/to/file2.xml"]; } );
dispatch_async(queue,^ { [self updateFromXMLFile:@"http://path/to/file3.xml"]; } );

dispatch_barrier_async(queue,^ {
    dispatch_async(dispatch_get_main_queue(),^ { 
            [self setBottomBarToUpdated]; 
    });
});

下面是函数updateFromXMLFile

- (BOOL) updateFromXMLFile:(NSString *)pathToFile {

         NSURL *url = [[NSURL alloc] initWithString:pathToFile];
         NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];

         XMLParser *parser = [[XMLParser alloc] initXMLParser];

         parser.managedObjectContext = self.managedObjectContext;

         [xmlParser setDelegate: parser];

         BOOL success = [xmlParser parse];

         if(success)
              return TRUE;
         else
              return FALSE;

}

我遇到的问题是这个错误消息:***Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0xc675e10> was mutated while being enumerated.'

我猜它与同时与我的 ManagedObjectContext 搞乱的所有进程有关。我不确定如何处理。有任何想法吗?谢谢!

【问题讨论】:

  • 强烈建议查看 WWDC 2012 核心数据会议,它们涵盖了所有线程方面的内容
  • 您是否尝试评论四个调度调用之一以检查它们中的任何一个是否专门造成了问题?我会从那里开始。另外,有没有办法可以序列化解析?你想过 NSOperationQueue 吗?

标签: ios xml ios5 xml-parsing grand-central-dispatch


【解决方案1】:

您需要展示您的 XMLParser 如何处理其委托职责的代码,因为这就是被调用的内容。

现在,您在不同的线程中同时解析多个文件。它们都调用不同的 XMLParser 对象,但每个都使用相同的托管对象上下文 (MOC)。

如果您使用默认的 MOC 设置,则需要将所有这些 MOC 调用重新召集到主线程(如果是,确实是您第一次创建上下文的位置)。如果你使用约束,并且你的 MOC 不是在主线程上创建的,那么你给自己带来了更多的麻烦。

但是,这是一个非常简单的修复。在该解析器委托方法中,每当您使用托管对象上下文时,将其包含在调用中以在主线程上调度该部分。

dispatch_async(dispatch_get_main_queue(), ^{
    // Put your code that accesses the MOC in here.
});

现在,要批量下载到 Core Data 中,您最好使用两种方法之一。

创建一个新的 MOC,直接连接到持久存储协调器,并在那里进行所有保存。您的主要 MOC 需要观察保存通知并合并这些更改。

或者,使新的 MOC 成为您的主 MOC 的子 MOC,并直接保存到其中,然后它可以保存。

但是,如果您的托管对象上下文是 NSMainQueueConcurrencyType 或 NSPrivateQueueConcurrency 类型(上述第二个选项的唯一方法),那么您可以使用它的 perfromBlock 方法...

[managedObjectContext performBlock:^{
    // Do your MOC stuff in here
}];

最重要的是,可以使用三种并发类型之一来创建 MOC。如果它是 NSConfinementConcurrencyType,那么你不能在创建它的线程之外触摸它。如果是 NSMainQueueConcurrencyType,则必须使用 performBlock* 或仅在主线程中触摸它。如果是 NSPrivateConcurrencyType,则必须使用 performBlock*。

【讨论】:

  • 解析文件太大了,我想放在这里。我需要清理它;/ -- 但是,我的 ManagedObjectContext 是 NSManagedObjectContext 类型,对于我正在解析的每种对象类型,我有 4 个不同的 NSManagedObject 子类。我认为我访问 MOC 的唯一地方是 @synthesize,一旦我关闭 xml 文件的库元素,[appDelegate saveContext] 就会调用四种对象类型中的每一种,我认为就是这样。我尝试了您的第一个建议,但似乎没有用,但仍然出现错误;/
  • 您不能以任何您喜欢的方式访问托管对象上下文。 Core Data 有非常严格的规则,必须遵守。任何时候您对 MOC(包括它所包含的托管对象)进行任何操作时,您都必须遵守规则。我在我提供的答案中总结了它们。
【解决方案2】:

问题是您不能使用并发队列来连接 MOC,您必须使用串行队列。将您的队列创建更改为

dispatch_queue_t queue = dispatch_queue_create("updateQueue", DISPATCH_QUEUE_SERIAL);

一切都会好起来的。同样,这样做的原因是最终的 XMLParser 对象同时尝试与 moc 交互。

您的选择取决于 XMLParser(或任何类)正在做什么。如果一个类在与 MOC 交互之前完成了繁重的工作,或者它从 Web 下载数据,那么您将通过并发获得一些好处。

我建议您从 URL 下载所需的数据,然后使用 MOC 串行工作。您必须在单个线程(在 iOS 中)上与 MOC 交互,但这不一定是 mainThread - 它可以是任何线程,只要它只是一个线程即可。这意味着如果您创建自己的串行队列,则必须在该队列上完成所有工作,包括创建 MOC!

回到您的代码。让我们假设您在 mainQueue 上完成所有核心数据工作(目前)。解决方案是将您的代码更改为:

dispatch_async(dispatch_get_global_queue(0,0) {
  NSURL *url = [[NSURL alloc] initWithString:pathToFile];
  NSData *data = [NSData dataWithContentsOfURL:url];
  NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithData:data];
  XMLParser *parser = [[XMLParser alloc] initXMLParser];
  parser.managedObjectContext = self.managedObjectContext;
  [xmlParser setDelegate: parser];

  dispatch_async(dispatch_get_main_queue(), {
         BOOL success = [xmlParser parse];

         // need some means to associate success/failure with the URL
  } );

您可以同时执行尽可能多的操作,即在点击 MOC 时进行序列化。当您为 MOC 使用串行队列时,唯一的区别是您将向该队列而不是 mainQueue 发送消息。

【讨论】:

  • 我不确定我是否看到并发队列的问题。这些文件使用完全独立的对象进行处理。唯一的问题是 MOC,即使是串行队列也是一个问题,因为无论何时您从多个线程访问 MOC,这始终是一个问题。
  • 我在代码中所做的是使用并发操作来处理数据,然后在串行队列上完成对 moc 的访问。关键是您的错误是由于 moc 的并发访问。这也让所有 moc 在它自己的线程中工作(因为串行队列不在主线程中)。
  • 无论如何,您可以更新您的答案以在您的最新评论中对您所说的内容进行更多描述吗?不确定我是否遵循...您不使用块来进行单个方法调用吗?
猜你喜欢
  • 2023-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-10
  • 1970-01-01
相关资源
最近更新 更多