iOS:如何在使用Grand Central Dispatch(GCD)时同步我的managedObjectContext?

时间:2012-08-14 15:37:48

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

我正在使用以下代码运行一个解析我的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同时搞乱的进程有关。我不知道如何处理它。有任何想法吗?谢谢!

2 个答案:

答案 0 :(得分:1)

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

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

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

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

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

返回您的代码。让我们假设您在mainQueue上执行所有Core Data工作(目前)。解决方案是将代码更改为:

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。

答案 1 :(得分: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的孩子,并直接保存到它,然后它可以保存。

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

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

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