"Коллекция была изменена во время перечисления" на executeFetchRequest



Я застрял на проблеме в течение нескольких часов и прочитав все об этом на stackoverflow (и применить все советы найдены), я теперь официально нуждается в помощи. ; o)



вот :



в моем проекте iPhone мне нужно импортировать данные в фоновом режиме и вставить их в контекст управляемого объекта. Следуя советам, найденным здесь, вот что я делаю:




  • сохранить основной moc

  • создать экземпляр фонового moc с помощью постоянный координатор хранилища, используемый главным moc

  • зарегистрируйте мой контроллер в качестве наблюдателя уведомления NSManagedObjectContextDidSaveNotification для фонового moc

  • вызовите метод импорта в фоновом потоке

  • каждый раз, когда данные получены, вставьте его на фоне moc

  • после того, как все данные были импортированы, сохраните фоновый moc

  • объединить изменения в основной moc, на главном нить

  • отменить регистрацию моего контроллера в качестве наблюдателя для уведомления

  • сброс и освобождение фонового moc


иногда (и случайно), за исключением...



*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5e0b930> was mutated while being enumerated...


...бросается при вызове executeFetchRequest на фоне moc, чтобы проверить, если импортированные данные уже существуют в базе данных. Интересно, что мутирует набор, так как нет ничего, что работает вне метода импорта.



Я включил весь код моего контроллера и моей тестовой сущности (мой проект, состоящий из этих двух классов и делегата приложения, который был немодифицирован):



//
// 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.



что может мутировать в этом случае ?



я почти уверен, что я упускаю что-то очевидное, и это ведет меня безумный.



EDIT:



вот полная трассировка стека :



    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'
512   2  

2 ответов:

хорошо, я думаю, что я решил свою проблему, и я должен поблагодарить это сообщение в блоге от Фреда Макканна:

http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/

проблема, похоже, связана с тем, что я создаю экземпляр своего фонового moc в основном потоке вместо фонового потока. Когда Apple говорит, что каждый поток должен иметь свой собственный moc, вы должны отнестись к этому серьезно : каждый moc должен быть создан в потоке, который будет используйте его !

перемещение следующих строк...

// We instantiate the background moc

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

[self.backgroundMOC setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];

...в методе _importData (непосредственно перед регистрацией контроллера в качестве наблюдателя для уведомления) решает проблему.

Спасибо за помощь, Питер. И спасибо Фреду Макканну за его ценный пост в блоге !

Я работал над импортом записей и отображением записей в tableview. Столкнулся с той же проблемой, когда я пытался сохранить запись на backgroundThread как ниже

 [self performSelectorInBackground:@selector(saveObjectContextInDataBaseWithContext:) withObject:privateQueueContext];

в то время как я уже создал PrivateQueueContext. Просто замените приведенный выше код на ниже один

[self saveObjectContextInDataBaseWithContext:privateQueueContext];

На самом деле это была моя глупая работа, чтобы сохранить на фоновом потоке, в то время как я уже создал privateQueueConcurrencyType для сохранения записи.

Comments

    Ничего не найдено.