2011-05-26 52 views
8

我有一个核心数据为基础的应用程序,它拥有一个对象(列表)关系的多个对象(列表项)关系。我正在设备之间同步数据,并且作为其中的一部分,我从后台线程中的XML文件导入列表(通过NSOperation子类)。核心数据/ NSOperation:通过枚举和删除对象时崩溃

当我更新现有列表时,我删除了它的所有旧列表项(从特定于该线程的NSManagedObjectContext中)并将它们替换为XML文件中的新列表项...删除通过枚举来处理该列表中的项目:

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

然而,曾经在一段时间,我是枚举过程中得到一个崩溃:

*终止应用程序由于未捕获的异常“NSGenericException”,原因是:“*收藏< _NSFaultingMutableSet:0x4fcfcb0>发生了变异,而bei枚举。

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

如果有帮助,下面是从我的NSOperation子类的“主要”功能(在这里我通过解析XML数据删除核心数据旧列表中的项目,并更新列表)的代码:

- (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]; 
} 

感谢您的任何建议。

回答

30

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

处理此问题的常见方法是创建集合的副本并枚举副本。天真的方法,它也只是:

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

但我发现核心数据处理是-copy实际上并不返回一个副本,但往往只是另一个断层代理时。所以我选择以这种方式复制集合:

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

似乎已经修复了它......经过20分钟的测试后,我再也没有看到错误。我以为我看过代码示例,人们使用快速枚举来删除Core Data关系中的对象,就像我正在做的那样 - 但显然这样做并不安全。如您所示,首先制作一套复制品更好。谢谢您的帮助。 – 2011-05-26 18:09:58