2010-08-10 4 views
117

Ich stehe seit Stunden auf einem Problem fest und nachdem ich alles über stackoverflow gelesen habe (und alle gefundenen Ratschläge anwende), bin ich nun offiziell auf Hilfe angewiesen. ; O)"Sammlung wurde mutiert, während sie aufgezählt wird" on executeFetchRequest

Hier ist der Kontext:

In meinem iPhone-Projekt, ich brauche Daten auf dem Hintergrund importieren und in einem verwalteten Objektkontext einfügen. Im Anschluss an die hier Ratschläge, hier ist, was ich tue:

  • Speichern, um die Haupt-moc
  • einen Hintergrund moc mit dem persistenten Speicher Koordinator von der Haupt moc
  • Registrieren als Beobachter meinen Controller verwendet Instantiate die NSManagedObjectContextDidSaveNotification Benachrichtigung für den Hintergrund moc
  • Aufruf des Import-Verfahren auf einem Hintergrund-Thread
  • Jedes Mal, wenn Daten empfangen wird, setzen sie sich auf dem Hintergrund moc
  • Sobald alle Daten importiert wurden, speichern Sie den Hintergrund moc
  • Führen Sie die Änderungen in das Haupt moc, auf dem Hauptthread
  • Unregister meinen Controller als Beobachter für die Benachrichtigung
  • zurücksetzen und lassen Sie den Hintergrund moc

Manchmal (und zufällig), die Ausnahme ...

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

... geworfen, wenn ich executeFetchRequest auf dem Hintergrund moc nennen, wenn die importierten Daten ALR zu überprüfen Es existiert in der Datenbank. Ich frage mich, was das Set mutiert, da es nichts gibt, was außerhalb der Importmethode läuft.

Ich habe den gesamten Code von meinem Controller und meine Testeinheit (mein Projekt aus diesen beiden Klassen und die AppDelegate, die unmodifizierte wurde) enthalten:

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

Das alles ist! Das ganze Projekt ist hier. Keine Tabellenansicht, kein NSFetchedResultsController, nichts anderes als ein Hintergrund-Thread, der Daten auf einem Hintergrund-Moc importiert.

Was könnte den Satz in diesem Fall mutieren?

Ich bin mir ziemlich sicher, dass ich etwas offensichtlich vermisse und es macht mich verrückt.

EDIT:

Hier ist der vollständige 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' 
+1

Aktivieren Sie im Xcode-Menü "Ausführen" die Option "Bei Objective-C-Ausnahmen stoppen" und führen Sie dann Ihre App unter dem Debugger aus. Was findest du? –

+1

Es bestätigt, dass die App in der Zeile "executeFetchRequest: error:" abstürzt. Ich habe die volle Stapelspur zu meiner ursprünglichen Frage hinzugefügt ... –

+0

Und was ist mit den anderen Threads? –

Antwort

175

OK, ich glaube, ich habe mein Problem gelöst und ich muß diese Blog-Post von Fred McCann dankt:

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

Das Problem scheint von der Tatsache zu kommen, dass ich meine Hintergrund-Moc auf den Haupt-Thread anstelle des Hintergrund-Thread instanziiere. Wenn Apple sagt, dass jeder Thread seinen eigenen Moc haben muss, muss man es ernst nehmen: Jeder Moc muss in dem Thread instanziiert werden, der ihn benutzen wird!

Verschieben der folgenden Zeilen ...

// We instantiate the background moc 

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

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

... in dem _importData Verfahren (kurz vor der Steuerung als Beobachter für die Benachrichtigung registriert) löst das Problem.

Danke für Ihre Hilfe, Peter. Und danke an Fred McCann für seinen wertvollen Blogbeitrag!

+2

OK, nach vielen Tests kann ich bestätigen, dass dies mein Problem absolut gelöst hat. Ich werde dies als akzeptierte Antwort markieren, sobald ich darf ... –

+0

Danke für diese Lösung! Dieser Thread hat eine sehr gute Implementierung von Lock/Unlock-Kontext, um Konflikte während der Zusammenführung zu vermeiden: http://stackoverflow.com/questions/2009399/cryptic-error-from-core-data-nsinvalidargumenttextexception-reason-referencedata64 – gonso

+4

+1 Thanks a viel für das Aufstellen der Frage, die Lösung und die Bereitstellung des Links zu Fred McCanns Blog-Post .. Es hat mir sehr geholfen !!! – learner2010

0

Ich arbeitete am Import von Datensatz & Anzeige von Datensätzen in der Tabellenansicht. Konfrontiert gleiche Problem, wenn ich versuchte Datensatz speichern auf backgroundThread wie unter

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

, während ich bereits eine PrivateQueueContext erstellt. Ersetzen Sie einfach oben Code mit unter einer

[self saveObjectContextInDataBaseWithContext:privateQueueContext]; 

Wirklich, es war meine dumme Arbeit auf Hintergrund-Thread zu speichern, während ich bereits eine privateQueueConcurrencyType zum Speicher Datensatz erstellt.

Verwandte Themen