在executeFetchRequest上枚举时“集合发生了变化”

我被困在一个问题上几个小时,并阅读了关于这个在stackoverflow(并应用每一个build议发现)的一切,我现在正式需要帮助。 ; O)

这里是上下文:

在我的iPhone项目中,我需要在后台导入数据并将其插入托pipe对象上下文中。 遵循这里发现的build议,这就是我正在做的事情:

  • 保存主要的moc
  • 使用主moc使用的持久性存储协调器实例化后台moc
  • 将我的控制器注册为后台moc的NSManagedObjectContextDidSaveNotification通知的观察者
  • 在后台线程上调用导入方法
  • 每次收到数据时,都将其插入到背景moc中
  • 一旦所有的数据已被导入,保存后台moc
  • 将更改合并到主线程中的主要moc中
  • 取消注册我的控制器作为通知的观察者
  • 重置并释放背景moc

有时(和随机),例外…

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5e0b930> was mutated while being enumerated... 

当我在后台moc上调用executeFetchRequest时,会引发…来检查导入的数据是否已经存在于数据库中。 我想知道什么是突变的设置,因为没有什么东西在导入方法外运行。

我已经包含了我的控制器和我的testing实体(我的项目由这两个类和应用程序委托,这是未经修改的)的完整代码:

 // // RootViewController.h // FK1 // // Created by Eric on 09/08/10. // Copyright (c) 2010 __MyCompanyName__. All rights reserved. // #import <CoreData/CoreData.h> @interface RootViewController : UITableViewController <NSFetchedResultsControllerDelegate> { NSManagedObjectContext *managedObjectContext; NSManagedObjectContext *backgroundMOC; } @property (nonatomic, retain) NSManagedObjectContext *managedObjectContext; @property (nonatomic, retain) NSManagedObjectContext *backgroundMOC; @end // // RootViewController.m // FK1 // // Created by Eric on 09/08/10. // Copyright (c) 2010 __MyCompanyName__. All rights reserved. // #import "RootViewController.h" #import "FK1Message.h" @implementation RootViewController @synthesize managedObjectContext; @synthesize backgroundMOC; - (void)viewDidLoad { [super viewDidLoad]; self.navigationController.toolbarHidden = NO; UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(refreshAction:)]; self.toolbarItems = [NSArray arrayWithObject:refreshButton]; } #pragma mark - #pragma mark ACTIONS - (void)refreshAction:(id)sender { // If there already is an import running, we do nothing if (self.backgroundMOC != nil) { return; } // We save the main moc NSError *error = nil; if (![self.managedObjectContext save:&error]) { NSLog(@"error = %@", error); abort(); } // We instantiate the background moc self.backgroundMOC = [[[NSManagedObjectContext alloc] init] autorelease]; [self.backgroundMOC setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]]; // We call the fetch method in the background thread [self performSelectorInBackground:@selector(_importData) withObject:nil]; } - (void)_importData { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundMOCDidSave:) name:NSManagedObjectContextDidSaveNotification object:self.backgroundMOC]; FK1Message *message = nil; NSFetchRequest *fetchRequest = nil; NSEntityDescription *entity = [NSEntityDescription entityForName:@"FK1Message" inManagedObjectContext:self.backgroundMOC]; NSPredicate *predicate = nil; NSArray *results = nil; // fake import to keep this sample simple for (NSInteger index = 0; index < 20; index++) { predicate = [NSPredicate predicateWithFormat:@"msgId == %@", [NSString stringWithFormat:@"%d", index]]; fetchRequest = [[[NSFetchRequest alloc] init] autorelease]; [fetchRequest setEntity:entity]; [fetchRequest setPredicate:predicate]; // The following line sometimes randomly throw the exception : // *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5b71a00> was mutated while being enumerated. results = [self.backgroundMOC executeFetchRequest:fetchRequest error:NULL]; // If the message already exist, we retrieve it from the database // If it doesn't, we insert a new message in the database if ([results count] > 0) { message = [results objectAtIndex:0]; } else { message = [NSEntityDescription insertNewObjectForEntityForName:@"FK1Message" inManagedObjectContext:self.backgroundMOC]; message.msgId = [NSString stringWithFormat:@"%d", index]; } // We update the message message.updateDate = [NSDate date]; } // We save the background moc which trigger the backgroundMOCDidSave: method [self.backgroundMOC save:NULL]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.backgroundMOC]; [self.backgroundMOC reset]; self.backgroundMOC = nil; [pool drain]; } - (void)backgroundMOCDidSave:(NSNotification*)notification { if (![NSThread isMainThread]) { [self performSelectorOnMainThread:@selector(backgroundMOCDidSave:) withObject:notification waitUntilDone:YES]; return; } // We merge the background moc changes in the main moc [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification]; } @end // // FK1Message.h // FK1 // // Created by Eric on 09/08/10. // Copyright 2010 __MyCompanyName__. All rights reserved. // #import <CoreData/CoreData.h> @interface FK1Message : NSManagedObject { } @property (nonatomic, retain) NSString * msgId; @property (nonatomic, retain) NSDate * updateDate; @end // // FK1Message.m // FK1 // // Created by Eric on 09/08/10. // Copyright 2010 __MyCompanyName__. All rights reserved. // #import "FK1Message.h" @implementation FK1Message #pragma mark - #pragma mark PROPERTIES @dynamic msgId; @dynamic updateDate; @end 

这就是全部 ! 整个项目都在这里 没有表视图,没有NSFetchedResultsController,没有什么比在后台moc导入数据的后台线程。

在这种情况下,什么可以改变设置?

我很确定我错过了一些明显的东西,这让我很生气。

编辑:

这是完整的堆栈跟踪:

  2010-08-10 10:29:11.258 FK1[51419:1b6b] *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5d075b0> was mutated while being enumerated.<CFBasicHash 0x5d075b0 [0x25c6380]>{type = mutable set, count = 0, entries => } ' *** Call stack at first throw: ( 0 CoreFoundation 0x0255d919 __exceptionPreprocess + 185 1 libobjc.A.dylib 0x026ab5de objc_exception_throw + 47 2 CoreFoundation 0x0255d3d9 __NSFastEnumerationMutationHandler + 377 3 CoreData 0x02287702 -[NSManagedObjectContext executeFetchRequest:error:] + 4706 4 FK1 0x00002b1b -[RootViewController _fetchData] + 593 5 Foundation 0x01d662a8 -[NSThread main] + 81 6 Foundation 0x01d66234 __NSThread__main__ + 1387 7 libSystem.B.dylib 0x9587681d _pthread_start + 345 8 libSystem.B.dylib 0x958766a2 thread_start + 34 ) terminate called after throwing an instance of 'NSException' 

好的,我想我已经解决了我的问题,我必须感谢Fred McCann的博客文章:

http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/

这个问题似乎来自这样的事实,我在主线程而不是后台线程实例化我的背景moc。 当Apple告诉每个线程需要自己的moc时,你必须认真对待:每个moc必须在将要使用它的线程中实例化!

移动以下几行…

 // We instantiate the background moc self.backgroundMOC = [[[NSManagedObjectContext alloc] init] autorelease]; [self.backgroundMOC setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]]; 

…在_importData方法(就在将控制器注册为通知的观察者之前)解决了这个问题。

谢谢你的帮助,彼得。 感谢Fred McCann的博客!

我正在导入logging和显示在tableview中的logging。 面对同样的问题,当我试图在backgroundThread像下面保存logging

  [self performSelectorInBackground:@selector(saveObjectContextInDataBaseWithContext:) withObject:privateQueueContext]; 

而我已经创build了一个PrivateQueueContext。 只需用上面的代码replace下面的代码

 [self saveObjectContextInDataBaseWithContext:privateQueueContext]; 

真的,我已经创build了一个用于保存logging的privateQueueConcurrencyType来保存后台线程,这是我的愚蠢的工作。