简体   繁体   English

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

[英]iOS: How do I synchronize my managedObjectContext when using Grand Central Dispatch (GCD)?

I'm using the following code to run a function that parses my xml files... 我正在使用以下代码来运行一个解析我的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]; 
    });
});

Here below is the function updateFromXMLFile : 下面是函数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;

}

The problem I'm coming across is this error message: ***Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0xc675e10> was mutated while being enumerated.' 我遇到的问题是这个错误消息: ***Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0xc675e10> was mutated while being enumerated.'

I'm guessing that it has something to do with all the processes messing with my ManagedObjectContext at the same time. 我猜它与所有与我的ManagedObjectContext同时搞乱的进程有关。 I'm not sure how to handle that though. 我不知道如何处理它。 Any ideas? 有任何想法吗? Thanks! 谢谢!

The problem is you cannot use a concurrent queue to interface to a MOC, you have to use a serial one. 问题是你不能使用并发队列来连接MOC,你必须使用串行队列。 Change your queue creation to 将队列创建更改为

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

and all will be well. 一切都会好的。 Again, the reason for doing this is that the final XMLParser objects are concurrently trying to interact with the moc. 同样,这样做的原因是最终的XMLParser对象同时尝试与moc进行交互。

Your options are dictated by what XMLParser (or any class) is doing. 您的选项取决于XMLParser(或任何类)正在做什么。 If a class does its heavy lifting BEFORE it interacts with the MOC, or it downloads data from the web, then you would gain something by concurrency. 如果一个类在与MOC交互之前完成其繁重的工作,或者它从Web下载数据,那么你将通过并发获得一些东西。

What I would suggest you do is download the data you need from the URLS, then serially work with the MOC. 我建议你做的是从URLS下载你需要的数据,然后连续使用MOC。 You must interact with the MOC on a single thread (in iOS) but that does NOT have to be the mainThread - it can be any thread as long as its just the one. 您必须在单个线程(在iOS中)与MOC交互,但不必是mainThread - 它可以是任何线程,只要它只是一个。 This means that if you create your own serial queue, everything has to be done on that queue including creating the MOC! 这意味着如果您创建自己的串行队列,则必须在该队列上完成所有操作,包括创建MOC!

Back to your code. 回到你的代码。 Lets assume that you do all Core Data work on the mainQueue (for now). 让我们假设您在mainQueue上执行所有Core Data工作(目前)。 The solution is change your code to this: 解决方案是将您的代码更改为:

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
  } );

You do as much as you can concurrently, the serialize when hitting the MOC. 您可以同时执行,在命中MOC时进行序列化。 The only difference when you use a serial queue for the MOC is that your would message that queue instead of the mainQueue. 使用串行队列进行MOC时,唯一的区别是您将发送该队列而不是mainQueue。

You need to show the code for how your XMLParser handles its delegate responsibilities, because that's what's being called. 您需要显示XMLParser如何处理其委托职责的代码,因为这就是所调用的内容。

Now, you are parsing multiple files at the same time, from within separate threads. 现在,您正在从单独的线程中同时解析多个文件。 They are all calling into different XMLParser objects, but each of those is using the same managed object context (MOC). 它们都调用不同的XMLParser对象,但每个对象都使用相同的托管对象上下文(MOC)。

If you are using the default MOC setup, you need to reconvene all those MOC calls to the main thread (if that is, indeed where you first created your context). 如果您使用的是默认的MOC设置,则需要重新召集所有那些对主线程的MOC调用(如果是这样,确实是您首次创建上下文的位置)。 If you use confinement, and your MOC is not created on the main thread, then you are giving yourself more trouble. 如果您使用限制,并且您的MOC未在主线程上创建,那么您将给自己带来更多麻烦。

However, it's a pretty simple fix. 但是,这是一个非常简单的修复。 In that parser delegate method, whenever you use the managed object context, enclose it in a call to dispatch that part on the main thread. 在该解析器委托方法中,每当您使用托管对象上下文时,将其包含在调用中以在主线程上分派该部分。

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

Now, for doing bulk downloads into Core Data, you are probably better off using one of two approaches. 现在,要进行批量下载到Core Data,最好使用两种方法之一。

Create a new MOC, attached directly to the persistent store coordinator, and do all your saving there. 创建一个新的MOC,直接连接到持久性存储协调器,并在那里进行所有保存。 Your main MOC needs to observe Save notifications and merge those changes. 您的主要MOC需要观察保存通知并合并这些更改。

Or, make the new MOC be a child of your main MOC, and save directly into it, then it can save. 或者,让新的MOC成为您的主要MOC的子项,并直接保存到其中,然后它可以保存。

If, however, your managed object context is of either NSMainQueueConcurrencyType or NSPrivateQueueConcurrency type (only way to do second option above), then you can just use its perfromBlock method... 但是,如果您的托管对象上下文是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. 底线是可以使用三种并发类型之一创建MOC。 If it is NSConfinementConcurrencyType, then you must not touch it outside the thread in which it was created. 如果是NSConfinementConcurrencyType,则不得在创建它的线程外触摸它。 If it is NSMainQueueConcurrencyType, you have to use performBlock* or only touch it in the main thread. 如果是NSMainQueueConcurrencyType,则必须使用performBlock *或仅在主线程中触摸它。 If it is NSPrivateConcurrencyType, you must use performBlock*. 如果是NSPrivateConcurrencyType,则必须使用performBlock *。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM