“La Collection a été muté, tout en étant énumérés” sur executeFetchRequest

Je suis bloqué sur un problème pendant des heures maintenant et avoir tout lu à ce sujet sur stackoverflow (et d'appliquer tous les conseils trouvé), je suis maintenant officiellement dans le besoin pour les aider. ;o)

Voici le contexte :

Dans mon iPhone projet, j'ai besoin d'importer des données sur l'arrière-plan et de l'insérer dans un objet géré contexte. Suivant les conseils trouvés ici, ici est ce que je fais :

  • Enregistrer le principal moc
  • Instancier un fond mémorandum de coopération avec le magasin persistant coordonnateur utilisé par le principal moc
  • Inscrire mon contrôleur en tant qu'observateur de la NSManagedObjectContextDidSaveNotification de notification pour l'arrière-plan moc
  • Appeler la méthode d'importation sur un thread d'arrière-plan
  • Chaque fois que des données sont reçues, insérez-la dans le fond moc
  • Une fois que toutes les données ont été importées, enregistrer l'arrière-plan moc
  • Fusionner les modifications dans les principaux moc, sur le thread principal
  • Annuler l'inscription de mon contrôleur en tant qu'observateur pour la notification
  • Reset et la libération de l'arrière-plan moc

Parfois (et au hasard), à l'exception...

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

...est renvoyée lorsque j'appelle executeFetchRequest sur le fond moc, pour vérifier si les données importées existe déjà dans la base de données. Je me demande quelle est la mutation de l'ensemble puisqu'il n'y a rien qui s'exécutent en dehors de la méthode d'importation.

J'ai inclus le code complet de mon contrôleur et mon test de l'entité (mon projet composé de ces deux classes et le délégué d'application, qui a été non modifié) :

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

C'est tout ! L'ensemble du projet est ici. Aucun tableau, aucune NSFetchedResultsController, rien d'autre qu'un thread d'arrière-plan que l'importation des données sur un fond de moc.

Ce qui pourrait muter le jeu dans ce cas ?

Je suis assez sûr que je suis absent quelque chose d'évident et ça me rend fou.

EDIT:

On a la trace de la pile :

    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'
  • Dans Xcode du menu Exécuter, activez l'option “Arrêt sur Objective-C Exceptions”, puis exécutez votre application sous le Débogueur. Que trouvez-vous?
  • Il confirme que l'application crash sur le "executeFetchRequest:erreur:" la ligne. J'ai ajouté le plein de trace de pile à ma question initiale...
  • Et pour les autres threads?
  • Hmmm, voici le thread principal de la pile : #0 0x958490fa dans mach_msg_trap #1 0x95849867 dans mach_msg #2 0x0253f206 dans __CFRunLoopServiceMachPort #3 0x0249c8b4 dans __CFRunLoopRun #4 0x0249c280 dans CFRunLoopRunSpecific #5 0x0249c1a1 dans CFRunLoopRunInMode #6 0x027a82c8 dans GSEventRunModal #7 0x027a838d dans GSEventRun #8 0x00021b58 dans UIApplicationMain #9 0x00001edc dans le main à main.m:16 Il ya 2 autres fils (libdispatch-manager et "WebThread"), mais ils ne donnent pas plus d'informations.
InformationsquelleAutor Eric MORAND | 2010-08-10