【发布时间】:2011-03-27 16:40:21
【问题描述】:
我被一个问题困扰了好几个小时,并且在 stackoverflow 上阅读了所有有关此问题的内容(并应用了找到的所有建议),我现在正式需要帮助。 ;o)
这里是上下文:
在我的 iPhone 项目中,我需要在后台导入数据并将其插入到托管对象上下文中。按照此处找到的建议,这就是我正在做的事情:
- 保存主 moc
- 使用主 moc 使用的持久存储协调器实例化后台 moc
- 将我的控制器注册为后台 moc 的 NSManagedObjectContextDidSaveNotification 通知的观察者
- 在后台线程调用导入方法
- 每次收到数据,在后台moc插入
- 所有数据导入后,保存后台moc
- 在主线程上将更改合并到主 moc 中
- 取消将我的控制器注册为通知的观察者
- 重置并释放后台 moc
有时(并且是随机的)例外情况...
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5e0b930> was mutated while being enumerated...
...当我在后台 moc 上调用 executeFetchRequest 时抛出,以检查导入的数据是否已存在于数据库中。我想知道是什么在改变集合,因为在导入方法之外没有任何东西运行。
我已经包含了我的控制器和我的测试实体的全部代码(我的项目由这两个类和未修改的应用程序委托组成):
//
// 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'
【问题讨论】:
-
在 Xcode 的 Run 菜单中,打开“Stop on Objective-C Exceptions”,然后在 Debugger 下运行你的应用程序。你发现了什么?
-
它确认应用程序在“executeFetchRequest:error:”行崩溃。我已将完整的堆栈跟踪添加到我原来的问题中......
-
其他线程呢?
-
嗯,这里是主线程堆栈:#0 0x958490fa在mach_msg_trap#1 0x95849867在mach_msg#2 0x0253f206在__CFRunLoopServiceMachPort#在__CFRunLoopRun 3 0x0249c8b4#4 0x0249c280在CFRunLoopRunSpecific#5 0x0249c1a1在CFRunLoopRunInMode#6 0x027a82c8 in GSEventRunModal #7 0x027a838d in GSEventRun #8 0x00021b58 in UIApplicationMain #9 0x00001edc in main at main.m:16 还有 2 个其他线程(libdispatch-manager 和“WebThread”),但它们没有提供更多信息。
标签: multithreading cocoa core-data