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

问题描述

|| 我有一个基于核心数据的应用程序,该应用程序具有一个对象(列表)与许多对象(列表项)的关系。我正在努力在设备之间同步数据,并且作为其中一部分,我从后台线程中的XML文件中导入列表(通过NSOperation子类)。 当我更新现有列表时,我删除了所有旧列表项(从特定于该线程的NSManagedobjectContext中),并用XML文件中的新列表项替换了它们。删除操作通过枚举以下项来处理:该列表:
for (ListItemCD *item in listToUpdate.listItems) {
    [self.importContext deleteObject:item];
}
但是,有时,在枚举期间会崩溃: *由于未捕获的异常\'NSGenericException \'而终止应用程序,原因:\'*集合<_NSFaultingMutableSet:0x4fcfcb0>在枚举时发生了突变。 我不确定从哪里开始寻找导致该问题的原因。枚举发生时,我不会在代码的任何其他部分修改列表。可以同时存在多个线程,因为导入/更新了不同的列表...会将上下文保存在另一个线程中会导致问题-因为它还会通知主上下文(如果它与枚举同时发生) ? 如果有帮助,这是我的NSOperation子类的“ main”函数代码在这里我从Core Data中删除旧列表项,并通过解析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];
}
感谢您的任何建议。     

解决方法

错误消息中有一些线索,您可以在其中看到列出的类“ 2”。确实,您要枚举的集合实际上只是一对多关系的代理,这将潜在地按需加载数据。由于在枚举过程中集合中的项目被标记为已删除,因此在枚举集合时某些集合可能会“更改”,并且您会看到该错误。 解决此问题的常用方法是创建集合的副本并枚举副本。天真的方法是:
NSSet *iterItems = [[list.items copy] autorelease];
for (ListItemCD *item in iterItems) { ... }
但是我发现在处理Core Data时,“ 4”实际上并不返回副本,而通常只是另一个错误的代理。因此,我选择以这种方式复制集合:
NSSet *iterItems = [NSSet setWithSet:list.items];
for (ListItemCD *item in iterItems) { ... }