0

我正在使用以下代码运行解析我的 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 混淆的所有进程有关。我不确定如何处理。有任何想法吗?谢谢!

4

2 回答 2

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 上完成所有核心数据工作(目前)。解决方案是将您的代码更改为:

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 发送消息。

于 2012-08-14T16:06:55.503 回答
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
}];

The bottom line is that a MOC can be created with one of three concurrency types. If it is NSConfinementConcurrencyType, then you must not touch it outside the thread in which it was created. If it is NSMainQueueConcurrencyType, you have to use performBlock* or only touch it in the main thread. If it is NSPrivateConcurrencyType, you must use performBlock*.

于 2012-08-14T16:14:42.990 回答