2011-05-26 11 views
8

Tengo una aplicación basada en datos básicos que tiene una relación de un objeto (una lista) para muchos objetos (elementos de lista). Estoy trabajando en la sincronización de datos entre dispositivos y, como parte de eso, importo listas de archivos XML en hilos de fondo (a través de una subclase de NSOperation).Core Data/NSOperation: bloqueo al enumerar y eliminar objetos

Cuando estoy actualizando una lista existente, elimino todos sus elementos de la lista anterior (del NSManagedObjectContext específico de esa cadena) y los reemplazo con archivos nuevos del archivo XML ... la eliminación se realiza enumerando a través de los elementos de esa lista:

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

Sin embargo, de vez en cuando, aparece un accidente durante esa enumeración:

* terminación de aplicación debido a excepción no detectada 'NSGenericException', razón: '* Colección < _NSFaultingMutableSet: 0x4fcfcb0> fue mutado mientras bei ng enumerado.

No estoy seguro por dónde empezar a buscar la causa de ese problema. No modifico la lista en ninguna otra parte del código mientras la enumeración está sucediendo. Puede haber múltiples hilos al mismo tiempo, ya que se importan/actualizan diferentes listas ... sería un problema guardar el contexto en otro hilo, ya que también notifica el contexto principal (si sucedió al mismo tiempo que la enumeración) ?

Si ayuda, aquí está el código de la función "principal" de mi subclase NSOperation (donde elimino los viejos elementos de la lista de la base de datos, y actualizar la lista mediante el análisis de los datos 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]; 
} 

Gracias por cualquier consejo.

Respuesta

30

Hay una pequeña pista en el mensaje de error, donde puede ver la clase NSFaultingMutableSet en la lista. De hecho, el conjunto que está enumerando es en realidad solo un proxy de la relación de muchos que potencialmente cargará datos a pedido. Como los elementos de la colección se marcan como borrados durante la enumeración, existe la posibilidad de que parte de la colección "cambie" mientras la enumera y verá ese error.

Una forma común de resolver esto es crear una copia de la colección y enumerar la copia. El enfoque ingenuo que sólo sería:

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

pero he encontrado cuando se trata de datos básicos que -copy en realidad no devolver una copia, pero a menudo sólo otro proxy fallas. Por lo tanto, prefiero copiar la colección de esta manera:

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

Parece que lo arreglé ... después de 20 minutos de prueba no volví a ver el error. Pensé que había visto ejemplos de código donde las personas usaban la enumeración rápida para eliminar objetos en una relación de Datos centrales como lo estaba haciendo, pero claramente no es seguro hacerlo. Es mucho mejor hacer una copia del conjunto primero como lo has mostrado. Gracias por la ayuda. –

Cuestiones relacionadas