“在被枚举时突变集合” on executeFetchRequest [英] "Collection was mutated while being enumerated" on executeFetchRequest

查看:189
本文介绍了“在被枚举时突变集合” on executeFetchRequest的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了一个问题,现在几个小时,在stackoverflow上阅读一切(并应用所有的建议),我现在正式需要帮助。 ; o)

I'm stuck on a problem for hours now and having read everything about this on stackoverflow (and apply every advices found), I'm now officially in need for help. ;o)

这里是上下文:

在我的iPhone项目中,我需要导入数据背景并将其插入受管对象上下文中。根据这里找到的建议,这里是我在做什么:

In my iPhone project, I need to import data on the background and insert it in a managed object context. Following the advices found here, here is what I'm doing :


  • 保存主moc

  • 实例化主moc使用的持久存储协调器的后台moc

  • 注册我的控制器作为背景moc的NSManagedObjectContextDidSaveNotification通知的观察者

  • 在后台线程中调用导入方法

  • 每次收到数据时,将其插入后台moc

  • ,保存后台moc

  • 将更改合并到主线程上

  • 注销我的控制器作为通知的观察者

  • 重置并释放背景moc

  • Save the main moc
  • Instantiate a background moc with the persistent store coordinator used by the main moc
  • Register my controller as an observer of the NSManagedObjectContextDidSaveNotification notification for the background moc
  • Call the import method on a background thread
  • Each time data is received, insert it on the background moc
  • Once all the data has been imported, save the background moc
  • Merge the changes into the main moc, on the main thread
  • Unregister my controller as an observer for the notification
  • Reset and release the background moc

有时/ p>

Sometimes (and randomly), the exception...

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

...当我调用executeFetchRequest在后台moc ,以检查导入的数据是否已在数据库中存在。

...is thrown when I call executeFetchRequest on the background moc, to check if the imported data already exists in the database. I wonder what is mutating the set since there is nothing that run outside the import method.

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

I've included the entire code of my controller and my test entity (my project consisting of these two classes and the app delegate, which has been unmodified) :

//
//  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上的数据。

This is all ! The whole project is here. No table view, no NSFetchedResultsController, nothing else than a background thread that import data on a background moc.

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

What could mutate the set in this case ?

我确定我缺少一些显而易见的东西,它让我疯狂。

I'm pretty sure I'm missing something obvious and it's driving me mad.

编辑:

这是完整的堆栈跟踪:

    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'


推荐答案

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

OK, I think I've solved my problem and I must thank this blog post from Fred McCann's :

http://www.duckrowing.com/2010/03/11/using-core-多线程数据/

问题似乎来自于我在主线程而不是后台线程实例化了我的后台moc 。当Apple告诉每个线程需要有自己的moc时,你必须认真对待:每个moc必须在使用它的线程中被实例化。

The problem seems to come from the fact that I instantiate my background moc on the main thread instead of the background thread. When Apple tells that each thread needs to have its own moc, you have to take it seriously : each moc must be instantiated in the thread that will be using it !

移动以下行...

// We instantiate the background moc

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

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

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

...in the _importData method (just before to register the controller as observer for the notification) solves the problem.

感谢您的帮助,Peter。感谢Fred McCann为其宝贵的博客文章!

Thanks for your help, Peter. And thanks to Fred McCann's for its valuable blog post !

这篇关于“在被枚举时突变集合” on executeFetchRequest的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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