executeFetchRequest에서“컬렉션이 열거되는 동안 변경되었습니다”
나는 지금 몇 시간 동안 문제를 겪었고 stackoverflow에서 이것에 관한 모든 것을 읽었으며 (발견 된 모든 조언을 적용) 공식적으로 도움이 필요합니다. ;영형)
컨텍스트는 다음과 같습니다.
내 iPhone 프로젝트에서 배경의 데이터를 가져 와서 관리되는 개체 컨텍스트에 삽입해야합니다. 여기에있는 조언에 따라, 내가하고있는 일은 다음과 같습니다.
- 메인 moc 저장
- 주 moc에서 사용하는 영구 저장소 코디네이터를 사용하여 백그라운드 moc를 인스턴스화하십시오.
- 백그라운드 moc에 대한 NSManagedObjectContextDidSaveNotification 알림의 관찰자로 컨트롤러를 등록하십시오.
- 백그라운드 스레드에서 import 메소드 호출
- 데이터가 수신 될 때마다 백그라운드 moc에 삽입
- 모든 데이터를 가져온 후에는 배경 moc를 저장하십시오
- 메인 스레드의 메인 moc에 변경 사항을 병합
- 알림에 대한 관찰자로 컨트롤러를 등록 해제하십시오.
- 백그라운드 moc 재설정 및 해제
때때로 (그리고 무작위로) 예외는 ...
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5e0b930> was mutated while being enumerated...
... 배경 moc에서 executeFetchRequest를 호출하면 가져온 데이터가 이미 데이터베이스에 있는지 확인하기 위해 발생합니다. import 메소드 외부에서 실행되는 것이 없기 때문에 세트를 변경하는 것이 무엇인지 궁금합니다.
내 컨트롤러 및 테스트 엔터티 (이 두 클래스와 수정되지 않은 앱 대리자로 구성된 프로젝트)의 전체 코드를 포함했습니다.
//
// 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에서 데이터를 가져 오는 백그라운드 스레드 외에는 없습니다.
이 경우 세트를 변경시킬 수있는 것은 무엇입니까?
I'm pretty sure I'm missing something obvious and it's driving me mad.
EDIT:
Here is the full stack trace :
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'
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/
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 !
Moving the following lines...
// We instantiate the background moc
self.backgroundMOC = [[[NSManagedObjectContext alloc] init] autorelease];
[self.backgroundMOC setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];
...in the _importData method (just before to register the controller as observer for the notification) solves the problem.
Thanks for your help, Peter. And thanks to Fred McCann's for its valuable blog post !
I was working on importing of record & display of records in tableview. Faced same issue when I tried to save record on backgroundThread like below
[self performSelectorInBackground:@selector(saveObjectContextInDataBaseWithContext:) withObject:privateQueueContext];
while I already created a PrivateQueueContext. Just replace above code with below one
[self saveObjectContextInDataBaseWithContext:privateQueueContext];
Really it was my foolish work to save on background thread while I already created a privateQueueConcurrencyType for saving record.
'Programing' 카테고리의 다른 글
종단 간 테스트에 각도기 또는 Karma를 사용해야합니까? (0) | 2020.07.13 |
---|---|
Git post commit hook 설정 방법 (0) | 2020.07.13 |
'멀티 파트 식별자'란 무엇이며 바인딩 할 수없는 이유는 무엇입니까? (0) | 2020.07.13 |
Visual Studio에서 빌드 할 때 조건부로 32/64 비트 참조 사용 (0) | 2020.07.13 |
Google App Engine : Gql LIKE 쿼리를 수행 할 수 있습니까? (0) | 2020.07.13 |