2010-08-13 3 views
13

Ich habe eine Reihe von Posts über NSManagedObjectContext und Multithread-Anwendungen gelesen. Ich bin auch über das CoreDataBooks-Beispiel gegangen, um zu verstehen, wie separate Threads ihren eigenen NSManagedObjectContext benötigen und wie eine Sicherungsoperation mit dem Haupt-NSManagedObjectContext zusammengeführt wird. Ich fand das Beispiel gut, aber auch zu anwendungsspezifisch. Ich versuche das zu verallgemeinern und frage mich, ob meine Herangehensweise vernünftig ist.Generischer Ansatz für NSManagedObjectContext in Multi-Thread-Anwendung

Mein Ansatz besteht darin, eine generische Funktion zum Abrufen des NSManagedObjectContext für den aktuellen Thread zu haben. Die Funktion gibt den NSManagedObjectContext für den Hauptthread zurück, erstellt jedoch einen neuen (oder ruft ihn aus einem Cache ab), wenn er in einem anderen Thread aufgerufen wird. Das geht wie folgt:

+(NSManagedObjectContext *)managedObjectContext { 
    MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate]; 
    NSManagedObjectContext *moc = delegate.managedObjectContext; 

    NSThread *thread = [NSThread currentThread]; 

    if ([thread isMainThread]) { 
     return moc; 
    } 

    // a key to cache the context for the given thread 
    NSString *threadKey = [NSString stringWithFormat:@"%p", thread]; 

    // delegate.managedObjectContexts is a mutable dictionary in the app delegate 
    NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts; 

    if ([managedObjectContexts objectForKey:threadKey] == nil) { 
     // create a context for this thread 
     NSManagedObjectContext *threadContext = [[[NSManagedObjectContext alloc] init] autorelease]; 
     [threadContext setPersistentStoreCoordinator:[moc persistentStoreCoordinator]]; 
     // cache the context for this thread 
     [managedObjectContexts setObject:threadContext forKey:threadKey]; 
    } 

    return [managedObjectContexts objectForKey:threadKey]; 
} 

Speichern Operationen sind einfach, wenn sie aus dem Hauptthread aufgerufen werden. Speicheroperationen, die von anderen Threads aufgerufen werden, müssen im Hauptthread zusammengeführt werden. Dafür habe ich eine generische commit Funktion:

+(void)commit { 
    // get the moc for this thread 
    NSManagedObjectContext *moc = [self managedObjectContext]; 

    NSThread *thread = [NSThread currentThread]; 

    if ([thread isMainThread] == NO) { 
     // only observe notifications other than the main thread 
     [[NSNotificationCenter defaultCenter] addObserver:self 
              selector:@selector(contextDidSave:) 
               name:NSManagedObjectContextDidSaveNotification 
               object:moc]; 
    } 

    NSError *error; 
    if (![moc save:&error]) { 
     // fail 
    } 

    if ([thread isMainThread] == NO) { 
     [[NSNotificationCenter defaultCenter] removeObserver:self 
                name:NSManagedObjectContextDidSaveNotification 
                object:moc]; 
    } 
} 

In der contextDidSave: Funktion wir die Zusammenführung durchführen, durch die Benachrichtigung in commit aufgerufen, wenn.

+(void)contextDidSave:(NSNotification*)saveNotification { 
    MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate]; 
    NSManagedObjectContext *moc = delegate.managedObjectContext; 

    [moc performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) 
         withObject:saveNotification 
        waitUntilDone:YES]; 
} 

Schließlich haben wir den Cache NSManagedObjectContext mit diesem clean-up:

+(void)initialize { 
    [[NSNotificationCenter defaultCenter] addObserver:self 
              selector:@selector(threadExit) 
               name:NSThreadWillExitNotification 
               object:nil]; 
} 

+(void)threadExit { 
    MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate]; 
    NSString *threadKey = [NSString stringWithFormat:@"%p", [NSThread currentThread]]; 
    NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;  

    [managedObjectContexts removeObjectForKey:threadKey]; 
} 

Dies kompiliert und scheint zu funktionieren, aber ich weiß Threading Probleme Rennbedingungen aufgrund schwierig sein kann. Sieht jemand ein Problem mit diesem Ansatz?

Auch ich benutze dies aus dem Kontext einer asynchronen Anfrage (mit ASIHTTPRequest), die einige Daten von einem Server holt und aktualisiert und fügt den Speicher auf dem iPhone ein. Es scheint, dass NSThreadWillExitNotification nicht ausgelöst wird, nachdem die Anforderung abgeschlossen wurde, und derselbe Thread wird dann für nachfolgende Anforderungen verwendet. Dies bedeutet, dass derselbe NSManagedObjectContext für separate Anforderungen für denselben Thread verwendet wird. Ist das ein Problem?

+0

Chris, ich bin mit einem ähnlichen Multi Threading-Problem konfrontiert, während eine einzige NSManagedObjectContext im Haupt-Thread für alle Operationen in NSOperation Queue erstellt. Das Problem tritt auf, wenn jeder Thread versucht, den Kontext zu speichern. Die App stürzt zufällig ab und löst eine Ausnahme aus den Kerndaten aus. Ich dachte daran, diesen Kontext während der Verwendung aller Operationen zu sperren, so dass jeder von ihnen einen exklusiven Zugriff auf den Kontext hat. Ich habe gelesen, dass Ihr oben solution.sounds gud, bitte fügen Sie mir den neuen Code, den Sie für die Zusammenführung des Kontextes verwendet haben und auch kommentieren Sie bitte mit einem Schloss für playin –

Antwort

8

Ein Jahr nach der Veröffentlichung dieser Frage habe ich endlich ein Framework erstellt, um meine Arbeit mit Core Data zu verallgemeinern und zu vereinfachen. Es geht über die ursprüngliche Frage hinaus und fügt eine Reihe von Funktionen hinzu, um Core-Daten-Interaktionen viel einfacher zu machen.Details hier: https://github.com/chriscdn/RHManagedObject

0

Ich fand eine Lösung nach dem Verständnis des Problems besser. Meine Lösung geht nicht direkt auf die obige Frage ein, sondern geht auf das Problem ein, warum ich überhaupt mit Threads umgehen musste.

Meine Anwendung verwendet die ASIHTTPRequest-Bibliothek für asynchrone Anforderungen. Ich hole einige Daten vom Server und benutze die Delegate requestFinished Funktion, um meine Kerndatenobjekte hinzuzufügen, zu ändern oder zu löschen. Die requestFinished-Funktion wurde in einem anderen Thread ausgeführt, und ich nahm an, dass dies ein natürlicher Nebeneffekt von asynchronen Anforderungen war.

Nach dem Graben tiefer fand ich, dass ASIHTTPRequest absichtlich die Anforderung in einem separaten Thread ausgeführt wird, kann aber in meiner Unterklasse von ASIHTTPRequest außer Kraft gesetzt werden:

+(NSThread *)threadForRequest:(ASIHTTPRequest *)request { 
    return [NSThread mainThread]; 
} 

Diese kleine Änderung requestFinished im Hauptthread setzt, die beseitigt mein Bedürfnis, sich um Threads in meiner Anwendung zu kümmern.

+0

Ich bin mir nicht ganz sicher, habe ich verstanden. ASIHTTPRequest verwendet einen separaten Thread (eigentlich eine NSOperationQueue) für asynchrone Anfragen, führt aber auch immer requestFinished auf dem mainthread aus. (Im neuesten Code befindet sich diese Funktion in der callSelectorOnMainThread-Methode.) Ich kann jedoch keinen Nachteil für Ihre Lösung feststellen. – JosephH

+0

Ich habe festgestellt, dass requestFinished nicht im Hauptthread lief, bis ich diese drei Zeilen oben hinzugefügt habe. Ist das möglich mit ASIHTTPRequest? Ich benutze v1.7. – chris

+0

Sie haben Recht. Der ASIHTTPRequest-Delegat wird auf dem Hauptthread ausgeführt, aber ich implementierte eine Unterklasse von ASIHTTPRequest und setze meinen Code in die 'requestFinished:' Methode. Dies wird nicht unbedingt im Hauptthread aufgerufen. Vielen Dank – chris

Verwandte Themen