나는 몇 시간 동안 문제에 갇혀 있고 stackoverflow에서 이것에 대한 모든 것을 읽었고 (그리고 발견 된 모든 조언을 적용) 이제 공식적으로 도움이 필요합니다. ;영형)
컨텍스트는 다음과 같습니다.
내 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'
답변
좋아, 나는 내 문제를 해결했다고 생각하며 Fred McCann의 다음 블로그 게시물에 감사드립니다.
http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/
문제는 백그라운드 스레드 대신 메인 스레드에서 백그라운드 moc을 인스턴스화한다는 사실에서 비롯된 것 같습니다. 애플이 각 스레드가 자체 moc를 가져야한다고 말할 때, 당신은 그것을 진지하게 받아 들여야합니다 : 각 moc는 그것을 사용할 스레드에서 인스턴스화되어야합니다!
다음 줄 이동 …
// We instantiate the background moc
self.backgroundMOC = [[[NSManagedObjectContext alloc] init] autorelease];
[self.backgroundMOC setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];
… _importData 메서드에서 (알림에 대한 관찰자로 컨트롤러를 등록하기 직전) 문제를 해결합니다.
도와 주셔서 감사합니다, 피터 그리고 소중한 블로그 게시물에 대한 Fred McCann에게 감사드립니다!
답변
나는 테이블 뷰에서 레코드 가져 오기 및 레코드 표시 작업을하고있었습니다. 아래와 같이 backgroundThread에 레코드를 저장하려고 할 때 동일한 문제에 직면했습니다.
[self performSelectorInBackground:@selector(saveObjectContextInDataBaseWithContext:) withObject:privateQueueContext];
이미 PrivateQueueContext를 만들었습니다. 위의 코드를 아래 코드로 바꾸십시오.
[self saveObjectContextInDataBaseWithContext:privateQueueContext];
레코드를 저장하기 위해 이미 privateQueueConcurrencyType을 만들면서 백그라운드 스레드에 저장하는 것은 정말 어리석은 작업이었습니다.