Apple Developer Connection bestätigt den Fehler
Für Entwickler ist CoreData eine große Erleichterung: Es vereinfacht die Art und Weise stark, wie Programme beispielsweise mit einer SQLite3-Datenbank interagieren. Leider scheint Apple mit Mac OS X 10.6 ein großer Fehler unterlaufen zu sein. Unter bestimmten Bedingungen liefert die CoreData-Datenbank falsche Ergebnisse zurück. Dies lässt sich mit einem einfachen Programm nachstellen: Legt man 100.000 CoreData-Datenbank-Objekte (NSManagedObjects) an und verknüpft jedes dieser Objekte mit einem weiteren Objekt über eine 1-zu-1-Relation, funktioniert dies in 10.4, 10.5 und 10.6 anstandslos. Will man nun diese Objekte wieder aus der Datenbank abrufen (mittels eines NSFetchRequests) und fragt anschließend jedes dieser Objekte nach der Relation zu dem zweiten angelegt Objekt, erhält man unter Mac OS X 10.6 zufällig falsche Angaben. CoreData verhält sich bei zufälligen Objekten so, als ob die Relation nicht gesetzt sei.
Apple scheint hier ein großes Problem beim Verteilen dieser Aufgabe auf mehrere Prozessorkerne zu haben. Lässt man ein Testprogramm auf zwei Kernen unter Snow Leopard laufen, erhält man beim Abruf von 100.000 Objekten 0 bis 2 falsche Angaben zu den Relations. Wird das selbe Programm auf dem selben Rechner mit 8 aktivierten Kernen ausgeführt, erhält man 5-10 falsche Angaben. Unter Mac OS X 10.5 funktioniert das Testprogramm tadellos, unabhängig von den aktivierten Kernen.
Eine Anfrage über die Apple Developer Connection bestätigte unsere Vermutung: Es ist tatsächlich ein schwerwiegender und bisher unentdeckter Fehler in Mac OS X 10.6 Snow Leopard und auch in Mac OS X 10.6.1. Betroffen hiervon sind alle Anwendungen, die Core Data in Verbindung mit SQLite-Stores nutzen. Datenverlust ist unter Umständen nicht auszuschließen, unvorhersehbares Verhalten von Anwendungen bei großen Datenmengen ist wahrscheinlich, besonders auf Mac Pros.
Einen Workaround bietet die Apple Developer Connection aber an: In den User Defaults (der Präferenz-Datei) des betreffenden Programmes kann der Schlüssel "com.apple.CoreData.ConcurrentFetching" auf 0 gesetzt werden, dann stimmen die Angaben aus den Datenbankabfragen. Ein Problem bringt dies aber mit sich: Die Datenbank-Abfragen verlangsamen sich hierdurch, so dass sogar Mac OS X 10.5 bei diesen Funktionen schneller ist, als Mac OS X 10.6.
Im Folgenden finden Sie den Beispielcode.
Zuerst wird die Datenbank erstellt:
//Create MoM, Persistant Store Coordinator and Context
NSManagedObjectModel *managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"Relations_Test_DataModel" ofType:@"mom"]]];
NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: managedObjectModel];
[managedObjectModel release];
[persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:[@"~/Desktop/Relations Test Database" stringByExpandingTildeInPath]] options:nil error:NULL];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator: persistentStoreCoordinator];
//Insert many objects into the context
NSEntityDescription *object1EntityDescription = [NSEntityDescription entityForName:@"Object1" inManagedObjectContext:context];
NSEntityDescription *object2EntityDescription = [NSEntityDescription entityForName:@"Object2" inManagedObjectContext:context];
//Create objects of type Object1 and Object2 and set a one-to-one relation between the two
int i = 0;
while(i<100000)
{
NSManagedObject *object1 = [[NSManagedObject alloc] initWithEntity:object1EntityDescription insertIntoManagedObjectContext:context];
NSManagedObject *object2 = [[NSManagedObject alloc] initWithEntity:object2EntityDescription insertIntoManagedObjectContext:context];
//Set Relation
[object1 setValue:object2 forKey:@"object2rel"];
//Set some dummy data
[object1 setValue:[[NSNumber numberWithInt:i] stringValue] forKey:@"dummy1"];
[object1 setValue:@"Test Test Test" forKey:@"dummy2"];
[object1 setValue:@"Test Test Test" forKey:@"dummy3"];
[object1 setValue:@"Test Test Test" forKey:@"dummy4"];
[object1 setValue:@"Test Test Test" forKey:@"dummy5"];
//Release
[object1 release];
[object2 release];
i++;
}
//Save the Context
[context processPendingChanges];
[context save:NULL];
[persistentStoreCoordinator release];
[context release];
}
Anschließend wird das eigentliche Testprojekt ausgeführt:
//Create MoM, Persistant Store Coordinator and Context
NSManagedObjectModel *managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"Relations_Test_DataModel" ofType:@"mom"]]];
NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: managedObjectModel];
[managedObjectModel release];
[persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:[@"~/Desktop/Relations Test Database" stringByExpandingTildeInPath]] options:nil error:NULL];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator: persistentStoreCoordinator];
NSTimeInterval t = [NSDate timeIntervalSinceReferenceDate];
//Create Fetch Request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"Object1" inManagedObjectContext:context]];
//[request setIncludesPropertyValues:NO]; !!Works when this is disabled but very slow
NSArray *objects = [context executeFetchRequest:request error:NULL];
int x = 0;
int count = [objects count];
while(x
{
NSManagedObject *object = [objects objectAtIndex:x];
//Check for relations set to null
if(![object valueForKey:@"object2rel"])
NSLog(@"No Relation set for object \%@ \%@",[object valueForKey:@"dummy1"],[object objectID]);
x++;
}
NSLog(@"\%f",[NSDate timeIntervalSinceReferenceDate]-t);
[persistentStoreCoordinator release];
[context release];
}
Werbung: