在executeFetchRequest上“枚举时对集合进行了变异”


121

我一直困扰着一个小时,并且已经在stackoverflow上阅读了有关此问题的所有内容(并应用了发现的所有建议),现在我正式需要帮助。; o)

这是上下文:

在我的iPhone项目中,我需要在后台导入数据并将其插入到托管对象上下文中。遵循此处找到的建议,这是我正在做的事情:

  • 保存主moc
  • 使用主Moc使用的持久存储协调器实例化背景Moc
  • 将我的控制器注册为后台moc的NSManagedObjectContextDidSaveNotification通知的观察者
  • 在后台线程上调用import方法
  • 每次接收到数据时,将其插入后台Moc
  • 导入所有数据后,保存后台Moc
  • 将更改合并到主线程中的主线程中
  • 注销我的控制器作为通知的观察者
  • 重置并释放后台Moc

有时(随机),例外情况...

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

当我在后台Moc上调用executeFetchRequest来检查导入的数据是否已存在于数据库中时,会抛出...。我想知道是什么使集合发生了变化,因为没有任何方法可以在import方法之外运行。

我已经包含了控制器和测试实体的全部代码(我的项目由这两个类和应用程序委托组成,它们未经修改):

//
//  RootViewController.h
//  FK1
//
//  Created by Eric on 09/08/10.
//  Copyright (c) 2010 __MyCompanyName__. All rights reserved.
//


#import <CoreData/CoreData.h>

@interface RootViewController : UITableViewController <NSFetchedResultsControllerDelegate> {
    NSManagedObjectContext *managedObjectContext;
    NSManagedObjectContext *backgroundMOC;
}


@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain) NSManagedObjectContext *backgroundMOC;

@end


//
//  RootViewController.m
//  FK1
//
//  Created by Eric on 09/08/10.
//  Copyright (c) 2010 __MyCompanyName__. All rights reserved.
//


#import "RootViewController.h"
#import "FK1Message.h"

@implementation RootViewController

@synthesize managedObjectContext;
@synthesize backgroundMOC;

- (void)viewDidLoad {
    [super viewDidLoad];

    self.navigationController.toolbarHidden = NO;

    UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(refreshAction:)];

    self.toolbarItems = [NSArray arrayWithObject:refreshButton];
}

#pragma mark -
#pragma mark ACTIONS

- (void)refreshAction:(id)sender {
    // If there already is an import running, we do nothing

    if (self.backgroundMOC != nil) {
        return;
    }

    // We save the main moc

    NSError *error = nil;

    if (![self.managedObjectContext save:&error]) {
        NSLog(@"error = %@", error);

        abort();
    }

    // We instantiate the background moc

    self.backgroundMOC = [[[NSManagedObjectContext alloc] init] autorelease];

    [self.backgroundMOC setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];

    // We call the fetch method in the background thread

    [self performSelectorInBackground:@selector(_importData) withObject:nil];
}

- (void)_importData {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundMOCDidSave:) name:NSManagedObjectContextDidSaveNotification object:self.backgroundMOC];         

    FK1Message *message = nil;

    NSFetchRequest *fetchRequest = nil;
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"FK1Message" inManagedObjectContext:self.backgroundMOC];
    NSPredicate *predicate = nil;
    NSArray *results = nil;

    // fake import to keep this sample simple

    for (NSInteger index = 0; index < 20; index++) {
        predicate = [NSPredicate predicateWithFormat:@"msgId == %@", [NSString stringWithFormat:@"%d", index]];

        fetchRequest = [[[NSFetchRequest alloc] init] autorelease];

        [fetchRequest setEntity:entity];
        [fetchRequest setPredicate:predicate];

        // The following line sometimes randomly throw the exception :
        // *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5b71a00> was mutated while being enumerated.

        results = [self.backgroundMOC executeFetchRequest:fetchRequest error:NULL];

        // If the message already exist, we retrieve it from the database
        // If it doesn't, we insert a new message in the database

        if ([results count] > 0) {
            message = [results objectAtIndex:0];
        }
        else {
            message = [NSEntityDescription insertNewObjectForEntityForName:@"FK1Message" inManagedObjectContext:self.backgroundMOC];
            message.msgId = [NSString stringWithFormat:@"%d", index];
        }

        // We update the message

        message.updateDate = [NSDate date];
    }

    // We save the background moc which trigger the backgroundMOCDidSave: method

    [self.backgroundMOC save:NULL];

    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:self.backgroundMOC];

    [self.backgroundMOC reset]; self.backgroundMOC = nil;

    [pool drain];
}

- (void)backgroundMOCDidSave:(NSNotification*)notification {    
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(backgroundMOCDidSave:) withObject:notification waitUntilDone:YES];
        return;
    }

    // We merge the background moc changes in the main moc

    [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}

@end

//
//  FK1Message.h
//  FK1
//
//  Created by Eric on 09/08/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import <CoreData/CoreData.h>

@interface FK1Message :  NSManagedObject  
{
}

@property (nonatomic, retain) NSString * msgId;
@property (nonatomic, retain) NSDate * updateDate;

@end

// 
//  FK1Message.m
//  FK1
//
//  Created by Eric on 09/08/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import "FK1Message.h"

@implementation FK1Message 

#pragma mark -
#pragma mark PROPERTIES

@dynamic msgId;
@dynamic updateDate;

@end

这就是全部 !整个项目在这里。没有表视图,没有NSFetchedResultsController,除了在后台Moc上导入数据的后台线程外没有什么。

在这种情况下,有什么可以改变集合?

我很确定自己缺少明显的东西,这让我发疯了。

编辑:

这是完整的堆栈跟踪:

    2010-08-10 10:29:11.258 FK1[51419:1b6b] *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5d075b0> was mutated while being enumerated.<CFBasicHash 0x5d075b0 [0x25c6380]>{type = mutable set, count = 0,
entries =>
}
'
*** Call stack at first throw:
(
    0   CoreFoundation                      0x0255d919 __exceptionPreprocess + 185
    1   libobjc.A.dylib                     0x026ab5de objc_exception_throw + 47
    2   CoreFoundation                      0x0255d3d9 __NSFastEnumerationMutationHandler + 377
    3   CoreData                            0x02287702 -[NSManagedObjectContext executeFetchRequest:error:] + 4706
    4   FK1                                 0x00002b1b -[RootViewController _fetchData] + 593
    5   Foundation                          0x01d662a8 -[NSThread main] + 81
    6   Foundation                          0x01d66234 __NSThread__main__ + 1387
    7   libSystem.B.dylib                   0x9587681d _pthread_start + 345
    8   libSystem.B.dylib                   0x958766a2 thread_start + 34
)
terminate called after throwing an instance of 'NSException'

2
在Xcode的“运行”菜单中,打开“停止Objective-C异常”,然后在调试器下运行您的应用程序。你发现了什么?
Peter Hosey 2010年

1
它确认应用程序在“ executeFetchRequest:error:”行上崩溃。我已将完整的堆栈跟踪信息添加到我的原始问题中……
Eric MORAND 2010年

那其他线程呢?
Peter Hosey 2010年

嗯,这是主线程堆栈:#0 0x958490fa in mach_msg_trap#1 0x95849867 in mach_msg#2 0x0253f206 in __CFRunLoopServiceMachPort#3 0x0249c8b4 in __CFRunLoopRun#4 0x0249c280 CFCFRunLoopRunSpecific#5 0x0249 in2#7 0x0249c280 in_Run UIApplicationMain#9中的8 0x00021b58在main.m:16中的main中的0x00001edc:还有2个其他线程(libdispatch-manager和“ WebThread”),但是它们没有提供更多信息。
埃里克·莫兰德

Answers:


182

好的,我想我已经解决了我的问题,我必须感谢Fred McCann的这篇博客文章:

http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/

问题似乎来自以下事实:我在主线程而不是后台线程上实例化了背景moc。当Apple告诉每个线程需要有自己的moc时,您必须认真对待:每个moc必须在将要使用它的线程中实例化!

移动以下几行...

// We instantiate the background moc

self.backgroundMOC = [[[NSManagedObjectContext alloc] init] autorelease];

[self.backgroundMOC setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];

...在_importData方法中(就在将控制器注册为通知的观察者之前)解决了该问题。

谢谢您的帮助,彼得。感谢Fred McCann的宝贵博客文章!


2
好,经过大量测试,我可以确认这已经完全解决了我的问题。我将在允许的情况下尽快将其标记为已接受的答案
Eric MORAND 2010年

感谢您的解决方案!针对这个讨论有合并过程中一个很好的实现锁定/解锁背景下,以避免冲突:stackoverflow.com/questions/2009399/...
gonso

4
+1非常感谢您提出问题,解决方案并提供指向Fred McCann的博客文章的链接。这对我很有帮助!
Learner2010

3
each moc must be instantiated in the thread that will be using it我虽然对MOC只操作应该是在同一个线程,但创造了MOC本身也一样,如果这是一个私人MOC,相关队列尚不存在..
亚诺什

@János我在这里有同样的问题。您如何在将使用它的线程中实例化上下文?该线程尚不存在。我正在使用Swift,但我不明白“在_importData方法中移动”是什么意思。
Todanley '18年

0

我正在从事记录的导入和在表视图中显示记录的工作。当我尝试将记录保存在backgroundThread上时遇到相同的问题,如下所示

 [self performSelectorInBackground:@selector(saveObjectContextInDataBaseWithContext:) withObject:privateQueueContext];

而我已经创建了PrivateQueueContext。只需将上面的代码替换为下面的代码

[self saveObjectContextInDataBaseWithContext:privateQueueContext];

在我已经创建了privateQueueConcurrencyType用于保存记录的同时,保存后台线程确实是我的愚蠢工作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.