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