核心数据/ NSOperation:枚举通过和删除对象时崩溃 [英] Core Data/NSOperation: crash while enumerating through and deleting objects

查看:173
本文介绍了核心数据/ NSOperation:枚举通过和删除对象时崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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

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

但是,一段时间后,我在枚举期间遇到崩溃: / p>

*由于未捕获异常NSGenericException而终止应用程序,原因:'* 集合< _NSFaultingMutableSet:0x4fcfcb0>在枚举时发生了变异。 / p>

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



如果有帮助,下面是我的NSOperation子类的main函数的代码(其中我从Core Data中删除旧的列表项,解析XML数据):

   - (void)main {

//输入xml数据into GDataXML
NSData * xmlData = [[NSMutableData alloc] initWithContentsOfFile:self.filePath];
NSError * error;
GDataXMLDocument * doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:& error];



//获取列表名称(以便我知道要更新的列表)
NSString * listName;
NSArray * listNames = [doc.rootElement elementsForName:@listName];
if(listNames.count> 0){
GDataXMLElement * listNameElement =(GDataXMLElement *)[li​​stNames objectAtIndex:0];
listName = listNameElement.stringValue;
// NSLog(@listName:%@,listName);




//执行提取以查找具有相同名称的旧列表(如果有)
NSFetchRequest * fetchRequest = [[ NSFetchRequest alloc] init];
NSEntityDescription * entity = [NSEntityDescription entityForName:@SubListCDinManagedObjectContext: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([ fetchedObjects count] == 1){
SubListCD * listToUpdate = [fetchedObjects objectAtIndex:0];

//获取列表图标名称
NSArray * listIconNames = [doc.rootElement elementsForName:@listIconName];
if(listIconNames.count> 0){
GDataXMLElement * listIconNameElement =(GDataXMLElement *)[li​​stIconNames objectAtIndex:0];
NSString * listIconName = listIconNameElement.stringValue;
// NSLog(@listIconName:%@,listIconName);
listToUpdate.listIconName = [NSString stringWithString:listIconName];
}

//获取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]];
}

//获取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]];
}

//获取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]];
}

//获取列表的创建日期
NSArray * listCreatedDates = [doc.rootElement elementsForName:@listDateCreated];
if(listCreatedDates.count> 0){
GDataXMLElement * listDateCreatedElement =(GDataXMLElement *)[li​​stCreatedDates objectAtIndex:0];
NSString * listDateCreated = listDateCreatedElement.stringValue;
// NSLog(@listDateCreated:%@,listDateCreated);
listToUpdate.dateCreated = [self dateFromString:listDateCreated];
}

//获取列表的修改日期
NSArray * listModifiedDates = [doc.rootElement elementsForName:@listDateModified];
if(listModifiedDates.count> 0){
GDataXMLElement * listDateModifiedElement =(GDataXMLElement *)[li​​stModifiedDates objectAtIndex:0];
NSString * listDateModified = listDateModifiedElement.stringValue;
// NSLog(@listDateModified:%@,listDateModified);
listToUpdate.dateModified = [self dateFromString:listDateModified];
}



//注意:可以从index.plist获取displayOrder,因为这些更新操作直到index.plist之后才被调用从Dropbox加载

//获取对文档目录的引用
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString * documentsDirectory = [paths objectAtIndex:0];

//获取index.plist文件的文件路径
NSString * indexFilePath = [documentsDirectory stringByAppendingPathComponent:@index.plist];

//使用索引中的列表名称构建一个数组
NSMutableArray * listsIndexArray = [NSMutableArray arrayWithContentsOfFile:indexFilePath];

int listIndex = [listsIndexArray indexOfObject:listName];

listToUpdate.displayOrder = [NSNumber numberWithInt:listIndex];





//从listToUpdate中删除旧的列表项,因为我将从XML文件中从头添加它们
for(ListItemCD * item in listToUpdate.listItems){
[self.importContext deleteObject:item];
}




//获取列表项的数组,所以我可以添加它们全部
NSArray * listItems = [doc .rootElement elementsForName:@item];
if(listItems.count> 0){
int counter = 0;
for(GDataXMLElement * item in listItems){

//创建新项目
ListItemCD * newItem = [NSEntityDescription insertNewObjectForEntityForName:@ListItemCDinManagedObjectContext:self.importContext];

//项目名称
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:检查我的日期..不知道这是否会滞留在其他地区

//项目创建日期
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;

//项目修改日期
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;

//项目完成日期
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;


//显示顺序
newItem.displayOrder = [NSNumber numberWithInt:counter];
counter ++;


//将新项目分配给listToUpdate
newItem.list = listToUpdate;
}
}



//现在已导入列表,因此将isUpdating设置为NO
listToUpdate.isUpdating = [NSNumber numberWithBool:NO];



//保存上下文。
NSError * saveError = nil;
if(![self.importContext save:& saveError]){
NSLog(@未解析的错误%@,%@,saveError,[saveError userInfo]);
abort();
}
else {
NSLog(@在同步更新列表后保存);
}


}
else {
NSLog(@UpdateOperation - 无法找到要更新的旧版本的列表!:%@ , 列表名称);
}
}

[doc release];
[xmlData release];
}

感谢任何建议。

解决方案

错误消息中有一点线索,您可以在其中看到 NSFaultingMutableSet 列表。事实上,你枚举的集合实际上只是一个对多对数关系的代理,可能需要加载数据。由于集合中的项目在枚举期间被标记为已删除,因此有可能存在某些集合在枚举时会更改,您会看到此错误。



一个常见的处理方法是创建集合的副本并枚举副本。对此的朴素方法只是:

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

在处理Core Data时发现, -copy 实际上不会返回副本,而通常只返回另一个故障代理。所以我选择以这种方式复制集合:

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


I have a core data based app that has a one object (a list) to many objects (list items) relationship. I'm working on syncing data between devices, and as part of that I import lists from XML files in background threads (via an NSOperation subclass).

When I'm updating an existing list, I delete all of its old list items (from the NSManagedObjectContext specific to that thread) and replace them with new ones from the XML file... the delete is handled by enumerating through the items for that list:

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

However, once in a while, I get a crash during that enumeration:

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

I'm not sure where to start looking for the cause of that problem. I don't modify the list in any other part of the code while the enumeration is happening. There can be multiple threads at the same time, as different lists are imported/updated... would saving the context in another thread cause an issue - since it also notifies the main context (if it happened at the same time as the enumeration)?

If it helps, here's the code from the "main" function of my NSOperation subclass (where I delete the old list items from Core Data, and update the list by parsing the XML data):

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

Thanks for any advice.

解决方案

There is a bit of a clue in the error message, where you can see the class NSFaultingMutableSet listed. Indeed the set you are enumerating is really just a proxy for the to-many relationship that will potentially load data on demand. Since items in the collection are being marked as deleted during the enumeration, the possibility exists that some of the collection will 'change' while you're enumerating it and you'll see that error.

A common way to deal with this is to create a copy of the collection and enumerate the copy. The naive approach to that would just be:

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

But I've found when dealing with Core Data that -copy does not actually return a copy but often just another faulting proxy. So I instead choose to copy the collection this way:

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

这篇关于核心数据/ NSOperation:枚举通过和删除对象时崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆