Multithreading &引用;集合在枚举时发生了变异;关于executeFetchRequest

Multithreading &引用;集合在枚举时发生了变异;关于executeFetchRequest,multithreading,cocoa,core-data,Multithreading,Cocoa,Core Data,我被一个问题困扰了好几个小时,在stackoverflow上阅读了关于这个问题的所有信息(并应用了所有发现的建议),我现在正式需要帮助;o) 以下是上下文: 在我的iPhone项目中,我需要在后台导入数据并将其插入托管对象上下文中。根据这里的建议,以下是我正在做的: 保存主主主运行中心 使用主moc使用的持久存储协调器实例化后台moc 将我的控制器注册为后台moc的NSManagedObjectContextDidSaveNotification通知的观察者 在后台线程上调用导入方法 每次收到

我被一个问题困扰了好几个小时,在stackoverflow上阅读了关于这个问题的所有信息(并应用了所有发现的建议),我现在正式需要帮助;o)

以下是上下文:

在我的iPhone项目中,我需要在后台导入数据并将其插入托管对象上下文中。根据这里的建议,以下是我正在做的:

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

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5e0b930> was mutated while being enumerated...
***由于未捕获的异常“NSGenericeException”而终止应用程序,原因:“***集合在枚举时发生了变异。。。
…在后台moc上调用executeFetchRequest时抛出,以检查导入的数据是否已存在于数据库中。我想知道是什么在改变集合,因为在导入方法之外没有运行任何东西

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

//
//RootViewController.h
//FK1
//
//由Eric于2010年9月8日创建。
//版权所有(c)2010年uu MyCompanyName uu。版权所有。
//
#进口
@接口RootViewController:UITableViewController{
NSManagedObjectContext*managedObjectContext;
NSManagedObjectContext*背景MOC;
}
@属性(非原子,保留)NSManagedObjectContext*managedObjectContext;
@属性(非原子,保留)NSManagedObjectContext*backgroundMOC;
@结束
//
//RootViewController.m
//FK1
//
//由Eric于2010年9月8日创建。
//版权所有(c)2010年uu MyCompanyName uu。版权所有。
//
#导入“RootViewController.h”
#导入“FK1Message.h”
@RootViewController的实现
@综合语境;
@合成背景MOC;
-(无效)viewDidLoad{
[超级视图下载];
self.navigationController.toolbarHidden=否;
UIBarButtonItem*refreshButton=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh目标:自我操作:@selector(refreshAction:)];
self.toolbarItems=[NSArray arrayWithObject:refreshButton];
}
#布拉格标记-
#pragma标记动作
-(无效)刷新操作:(id)发件人{
//如果已经有导入正在运行,我们什么也不做
如果(self.backgroundMOC!=nil){
返回;
}
//我们保存主主主运行中心
n错误*错误=nil;
如果(![self.managedObjectContext保存:&错误]){
NSLog(@“error=%@”,error);
中止();
}
//我们实例化了背景moc
self.backgroundMOC=[[[NSManagedObjectContext alloc]init]autorelease];
[self.backgroundMOC setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];
//我们在后台线程中调用fetch方法
[self-performSelectorInBackground:@selector(_importData)with object:nil];
}
-(无效)\u导入数据{
NSAutoreleasePool*池=[[NSAutoreleasePool alloc]init];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(backgroundMOCDidSave:)name:NSManagedObjectContextDidSaveNotification对象:self.backgroundMOC];
FK1Message*消息=nil;
NSFetchRequest*fetchRequest=nil;
NSEntityDescription*entity=[NSEntityDescription entityForName:@“FK1Message”在托管对象上下文中:self.backgroundMOC];
NSPredicate*谓词=nil;
NSArray*结果=无;
//假导入以保持此示例的简单性
对于(NSInteger索引=0;索引<20;索引++){
谓词=[NSPredicate PREDITEWITHFORMAT:@“msgId==%@,[NSString stringWithFormat:@“%d”,index]];
fetchRequest=[[NSFetchRequest alloc]init]autorelease];
[FetchRequestSetEntity:entity];
[FetchRequestSetPredicate:谓词];
//以下行有时会随机抛出异常:
//***由于未捕获的异常“NSGenericeException”而终止应用程序,原因:“***集合在枚举时发生了变异。
结果=[self.backgroundMOC executeFetchRequest:fetchRequest错误:NULL];
//如果消息已经存在,我们将从数据库中检索它
//如果没有,我们将在数据库中插入一条新消息
如果([结果计数]>0){
message=[results objectAtIndex:0];
}
否则{
message=[NSEntityDescription insertNewObjectForEntityForName:@“FK1Message”在托管对象上下文中:self.backgroundMOC];
message.msgId=[NSString stringWithFormat:@“%d”,索引];
}
//我们更新消息
message.updateDate=[NSDate];
}
//我们保存触发backgroundmocdisave:方法的后台moc
[self.backgroundMOC save:NULL];
[[NSNotificationCenter defaultCenter]removeObserver:self name:NSManagedObjectContextDidSaveNotification对象:self.backgroundMOC];
[self.backgroundMOC reset];self.backgroundMOC=nil;
[泳池排水沟];
}
-(无效)backgroundMOCDidSave:(NSNotification*)通知{
如果(![NSThread isMainThread]){
[self-performSelectorOnMainThread:@selector(backgroundMOCDidSave:)with object:notification waitUntilDone:YES];
返回;
}
//我们将背景moc更改合并到主moc中
[self.managedObjectContext合并更改fromContextDidSaveNotification:notification];
}
@结束
//
//FK1消息.h
//FK1
//
//创作者
//
//  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
    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'
// We instantiate the background moc

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

[self.backgroundMOC setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];
 [self performSelectorInBackground:@selector(saveObjectContextInDataBaseWithContext:) withObject:privateQueueContext];
[self saveObjectContextInDataBaseWithContext:privateQueueContext];