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

查看:33
本文介绍了“集合在被枚举时发生了突变"在 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 的 NSManagedObjectContextDidSaveNotification 通知的观察者
  • 在后台线程上调用导入方法
  • 每次接收到数据时,将其插入到后台moc
  • 导入所有数据后,保存后台 moc
  • 在主线程上将更改合并到主 moc 中
  • 取消将我的控制器注册为通知的观察者
  • 重置和释放后台 moc

有时(并且随机),例外...

Sometimes (and randomly), the exception...

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

... 当我在后台 moc 上调用 executeFetchRequest 时抛出,以检查导入的数据是否已存在于数据库中.我想知道是什么改变了集合,因为在导入方法之外没有运行任何东西.

...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-data-on-multiple-threads/

问题似乎来自于我在主线程而不是后台线程上实例化我的后台 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.

感谢您的帮助,彼得.并感谢 Fred McCann 的宝贵博文!

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

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

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