核心数据/ NSOperation:通过枚举和删除对象时崩溃

我有一个核心的数据为基础的应用程序,有一个对象(列表)的对象(列表项)的关系。 我正在设备之间同步数据,并作为我从后台线程中的XML文件导入列表(通过NSOperation子类)的一部分。

当我更新一个现有的列表,我删除它的所有旧的列表项(从特定于该线程的NSManagedObjectContext),并从XML文件中取代新的列表项…删除通过枚举通过项目列表:

for (ListItemCD *item in listToUpdate.listItems) { [self.importContext deleteObject:item]; } 

然而,偶尔有一次,我在枚举期间崩溃了:

*由于未捕获的exception“NSGenericException”而终止应用程序,原因:“* Collection <_NSFaultingMutableSet:0x4fcfcb0>在枚举时发生了变化。

我不确定从哪里开始寻找这个问题的原因。 枚举发生时,我不修改代码的任何其他部分中的列表。 可以有多个线程在同一时间,因为不同的列表导入/更新…将保存在另一个线程的上下文导致一个问题 – 因为它也通知主要上下文(如果它发生在同一时间的枚举) ?

如果有帮助,下面是我的NSOperation子类的“main”函数中的代码(我从Core Data中删除旧列表项,并通过parsingXML数据来更新列表):

 - (void)main { // input the xml data into GDataXML NSData *xmlData = [[NSMutableData alloc] initWithContentsOfFile:self.filePath]; NSError *error; GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error]; // get the list name (so that I know which list to update) NSString *listName; NSArray *listNames = [doc.rootElement elementsForName:@"listName"]; if (listNames.count > 0) { GDataXMLElement *listNameElement = (GDataXMLElement *) [listNames objectAtIndex:0]; listName = listNameElement.stringValue; // NSLog(@"listName: %@", listName); // perform a fetch to find the old list with the same name (if there is one) NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"SubListCD" inManagedObjectContext:self.importContext]; [fetchRequest setEntity:entity]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K like %@", @"listName", listName]; [fetchRequest setPredicate:predicate]; NSError *error; NSArray *fetchedObjects = [self.importContext executeFetchRequest:fetchRequest error:&error]; // NSLog(@"fetchedObjects count: %d", [fetchedObjects count]); [fetchRequest release]; /* // if I found the list, update its data */ if ([fetchedObjects count] == 1) { SubListCD *listToUpdate = [fetchedObjects objectAtIndex:0]; // get the list icon name NSArray *listIconNames = [doc.rootElement elementsForName:@"listIconName"]; if (listIconNames.count > 0) { GDataXMLElement *listIconNameElement = (GDataXMLElement *) [listIconNames objectAtIndex:0]; NSString *listIconName = listIconNameElement.stringValue; // NSLog(@"listIconName: %@", listIconName); listToUpdate.listIconName = [NSString stringWithString:listIconName]; } // get the isChecklist BOOL NSArray *isChecklistBools = [doc.rootElement elementsForName:@"isChecklist"]; if (isChecklistBools.count > 0) { GDataXMLElement *isChecklistElement = (GDataXMLElement *) [isChecklistBools objectAtIndex:0]; NSString *isChecklist = isChecklistElement.stringValue; // NSLog(@"isChecklist: %@", isChecklist); listToUpdate.isCheckList = [NSNumber numberWithBool:[isChecklist isEqualToString:@"YES"]]; } // get the itemsToTop BOOL NSArray *itemsToTopBools = [doc.rootElement elementsForName:@"itemsToTop"]; if (itemsToTopBools.count > 0) { GDataXMLElement *itemsToTopElement = (GDataXMLElement *) [itemsToTopBools objectAtIndex:0]; NSString *itemsToTop = itemsToTopElement.stringValue; // NSLog(@"itemsToTop: %@", itemsToTop); listToUpdate.itemsToTop = [NSNumber numberWithBool:[itemsToTop isEqualToString:@"YES"]]; } // get the includeInBadgeCount BOOL NSArray *includeInBadgeCountBools = [doc.rootElement elementsForName:@"includeInBadgeCount"]; if (includeInBadgeCountBools.count > 0) { GDataXMLElement *includeInBadgeCountElement = (GDataXMLElement *) [includeInBadgeCountBools objectAtIndex:0]; NSString *includeInBadgeCount = includeInBadgeCountElement.stringValue; // NSLog(@"includeInBadgeCount: %@", includeInBadgeCount); listToUpdate.includeInBadgeCount = [NSNumber numberWithBool:[includeInBadgeCount isEqualToString:@"YES"]]; } // get the list's creation date NSArray *listCreatedDates = [doc.rootElement elementsForName:@"listDateCreated"]; if (listCreatedDates.count > 0) { GDataXMLElement *listDateCreatedElement = (GDataXMLElement *) [listCreatedDates objectAtIndex:0]; NSString *listDateCreated = listDateCreatedElement.stringValue; // NSLog(@"listDateCreated: %@", listDateCreated); listToUpdate.dateCreated = [self dateFromString:listDateCreated]; } // get the list's modification date NSArray *listModifiedDates = [doc.rootElement elementsForName:@"listDateModified"]; if (listModifiedDates.count > 0) { GDataXMLElement *listDateModifiedElement = (GDataXMLElement *) [listModifiedDates objectAtIndex:0]; NSString *listDateModified = listDateModifiedElement.stringValue; // NSLog(@"listDateModified: %@", listDateModified); listToUpdate.dateModified = [self dateFromString:listDateModified]; } // NOTE: it's okay to get the displayOrder from index.plist here, since these update operations aren't called until after index.plist is loaded from Dropbox // get a reference to the documents directory NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; // get the file path of the index.plist file NSString *indexFilePath = [documentsDirectory stringByAppendingPathComponent:@"index.plist"]; // build an array with the names of the lists in the index NSMutableArray *listsIndexArray = [NSMutableArray arrayWithContentsOfFile:indexFilePath]; int listIndex = [listsIndexArray indexOfObject:listName]; listToUpdate.displayOrder = [NSNumber numberWithInt:listIndex]; // remove the old list items from the listToUpdate, since I'll be adding them from scratch from the XML file for (ListItemCD *item in listToUpdate.listItems) { [self.importContext deleteObject:item]; } // get an array of the list items so I can add them all NSArray *listItems = [doc.rootElement elementsForName:@"item"]; if (listItems.count > 0) { int counter = 0; for (GDataXMLElement *item in listItems) { // create the new item ListItemCD *newItem = [NSEntityDescription insertNewObjectForEntityForName:@"ListItemCD" inManagedObjectContext:self.importContext]; // item name NSArray *itemNames = [item elementsForName:@"itemName"]; if (itemNames.count > 0) { GDataXMLElement *itemNameElement = (GDataXMLElement *) [itemNames objectAtIndex:0]; NSString *itemName = itemNameElement.stringValue; // NSLog(@"itemName: %@", itemName); newItem.itemName = [NSString stringWithString:itemName]; } else continue; // item note NSArray *itemNotes = [item elementsForName:@"itemNote"]; if (itemNotes.count > 0) { GDataXMLElement *itemNoteElement = (GDataXMLElement *) [itemNotes objectAtIndex:0]; NSString *itemNote = itemNoteElement.stringValue; // NSLog(@"itemNote: %@", itemNote); newItem.itemNote = [NSString stringWithString:itemNote]; } else continue; // itemReadOnly BOOL NSArray *itemReadOnlyBools = [item elementsForName:@"itemReadOnly"]; if (itemReadOnlyBools.count > 0) { GDataXMLElement *itemReadOnlyElement = (GDataXMLElement *) [itemReadOnlyBools objectAtIndex:0]; NSString *itemReadOnly = itemReadOnlyElement.stringValue; // NSLog(@"itemReadOnly: %@", itemReadOnly); newItem.itemReadOnly = [NSNumber numberWithBool:[itemReadOnly isEqualToString:@"YES"]]; } else continue; // TODO: check my dates.. not sure if this will hold up in other locales // item creation date NSArray *itemCreatedDates = [item elementsForName:@"dateCreated"]; if (itemCreatedDates.count > 0) { GDataXMLElement *dateCreatedElement = (GDataXMLElement *) [itemCreatedDates objectAtIndex:0]; NSString *dateCreated = dateCreatedElement.stringValue; // NSLog(@"dateCreated: %@", dateCreated); newItem.dateCreated = [self dateFromString:dateCreated]; } else continue; // item modification date NSArray *itemModifiedDates = [item elementsForName:@"dateModified"]; if (itemModifiedDates.count > 0) { GDataXMLElement *dateModifiedElement = (GDataXMLElement *) [itemModifiedDates objectAtIndex:0]; NSString *dateModified = dateModifiedElement.stringValue; // NSLog(@"dateModified: %@", dateModified); newItem.dateModified = [self dateFromString:dateModified]; } else continue; // item completed BOOL NSArray *itemCompletedBools = [item elementsForName:@"itemCompleted"]; if (itemCompletedBools.count > 0) { GDataXMLElement *itemCompletedElement = (GDataXMLElement *) [itemCompletedBools objectAtIndex:0]; NSString *itemCompleted = itemCompletedElement.stringValue; // NSLog(@"itemCompleted: %@", itemCompleted); newItem.itemCompleted = [NSNumber numberWithBool:[itemCompleted isEqualToString:@"YES"]]; } else continue; // item completed date NSArray *itemCompletedDates = [item elementsForName:@"dateCompleted"]; if (itemCompletedDates.count > 0) { GDataXMLElement *dateCompletedElement = (GDataXMLElement *) [itemCompletedDates objectAtIndex:0]; NSString *dateCompleted = dateCompletedElement.stringValue; // NSLog(@"dateCompleted string: %@", dateCompleted); newItem.dateCompleted = [self dateFromString:dateCompleted]; // NSLog(@"dateCompleted: %@", newItem.dateCompleted); } else continue; // display order newItem.displayOrder = [NSNumber numberWithInt:counter]; counter++; // assign the new item to the listToUpdate newItem.list = listToUpdate; } } // the list is now imported, so set isUpdating back to NO listToUpdate.isUpdating = [NSNumber numberWithBool:NO]; // Save the context. NSError *saveError = nil; if (![self.importContext save:&saveError]) { NSLog(@"Unresolved error %@, %@", saveError, [saveError userInfo]); abort(); } else { NSLog(@"saved after UPDATING a list while syncing!"); } } else { NSLog(@"UpdateOperation - couldn't find an old version of the list to update!: %@", listName); } } [doc release]; [xmlData release]; } 

感谢您的任何build议。

错误消息中有一些线索,您可以在其中看到列出的NSFaultingMutableSet类。 事实上,你正在枚举的集合实际上只是一对多关系的代理,可能会按需加载数据。 由于集合中的项目在枚举过程中被标记为已删除,因此存在这样的可能性:某些集合将在枚举时“更改”,您将看到该错误。

处理这个问题的常用方法是创build一个集合的副本并枚举副本。 天真的做法就是:

 NSSet *iterItems = [[list.items copy] autorelease]; for (ListItemCD *item in iterItems) { ... } 

但是我发现,在处理核心数据时,拷贝实际上不会返回一个副本,而只能是另一个错误代理。 所以我select这样复制集合:

 NSSet *iterItems = [NSSet setWithSet:list.items]; for (ListItemCD *item in iterItems) { ... }