Ich habe jetzt stundenlang ein Problem und nachdem ich im Stackoverflow alles darüber gelesen habe (und alle gefundenen Ratschläge angewendet habe), brauche ich jetzt offiziell Hilfe. ;Ö)
Hier ist der Kontext:
In meinem iPhone-Projekt muss ich Daten im Hintergrund importieren und in einen verwalteten Objektkontext einfügen. Den folgenden Ratschlägen folgend, mache ich Folgendes:
- Speichern Sie den Haupt-Moc
- Instanziieren Sie einen Hintergrund-Moc mit dem persistenten Speicherkoordinator, der vom Haupt-Moc verwendet wird
- Registrieren Sie meinen Controller als Beobachter der Benachrichtigung NSManagedObjectContextDidSaveNotification für den Hintergrund-Moc
- Rufen Sie die Importmethode in einem Hintergrundthread auf
- Fügen Sie jedes Mal, wenn Daten empfangen werden, diese in den Hintergrund ein
- Speichern Sie nach dem Importieren aller Daten den Hintergrund-Moc
- Führen Sie die Änderungen im Haupt-Moc im Haupt-Thread zusammen
- Heben Sie die Registrierung meines Controllers als Beobachter für die Benachrichtigung auf
- Setzen Sie den Hintergrund-Moc zurück und lassen Sie ihn los
Manchmal (und zufällig) ist die Ausnahme ...
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x5e0b930> was mutated while being enumerated...
... wird ausgelöst, wenn ich executeFetchRequest im Hintergrund-Moc aufrufe, um zu überprüfen, ob die importierten Daten bereits in der Datenbank vorhanden sind. Ich frage mich, was die Menge mutiert, da außerhalb der Importmethode nichts ausgeführt wird.
Ich habe den gesamten Code meines Controllers und meiner Testentität eingefügt (mein Projekt besteht aus diesen beiden Klassen und dem App-Delegaten, der nicht geändert wurde):
//
// 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 ist alles ! Das ganze Projekt ist da. Keine Tabellenansicht, kein NSFetchedResultsController, nichts anderes als ein Hintergrundthread, der Daten in einen Hintergrund-Moc importiert.
Was könnte das Set in diesem Fall mutieren?
Ich bin mir ziemlich sicher, dass mir etwas Offensichtliches fehlt und es macht mich verrückt.
BEARBEITEN:
Hier ist die vollständige Stapelverfolgung:
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'
quelle
Antworten:
OK, ich denke, ich habe mein Problem gelöst und ich muss mich bei diesem Blog-Beitrag von Fred McCann bedanken:
http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/
Das Problem scheint von der Tatsache zu kommen, dass ich meinen Hintergrund-Moc auf dem Haupt-Thread anstelle des Hintergrund-Threads instanziiere. Wenn Apple sagt, dass jeder Thread einen eigenen Moc haben muss, muss man das ernst nehmen: Jeder Moc muss in dem Thread instanziiert werden, der ihn verwenden wird!
Verschieben der folgenden Zeilen ...
... in der _importData-Methode (kurz bevor der Controller als Beobachter für die Benachrichtigung registriert wird) löst das Problem.
Danke für deine Hilfe, Peter. Und danke an Fred McCann's für seinen wertvollen Blog-Beitrag!
quelle
each moc must be instantiated in the thread that will be using it
Ich denke, nur die Operation auf MOC sollte sich auf demselben Thread befinden, aber das MOC selbst zu erstellen, wenn dies ein privates MOC ist, ist die zugehörige Warteschlange noch nicht vorhanden.Ich habe am Importieren von Datensätzen und Anzeigen von Datensätzen in der Tabellenansicht gearbeitet. Beim Versuch, einen Datensatz auf backgroundThread wie unten zu speichern, trat dasselbe Problem auf
während ich bereits einen PrivateQueueContext erstellt habe. Ersetzen Sie einfach den obigen Code durch den folgenden
Es war wirklich meine dumme Arbeit, im Hintergrund-Thread zu speichern, während ich bereits einen privateQueueConcurrencyType zum Speichern von Datensätzen erstellt habe.
quelle