Multithread-Kerndaten

Wie Sie wissen, handelt es sich bei Core Data um ein leistungsstarkes Apple-Framework für die Verwaltung von Objektgrafiken. Es gibt viele Artikel über Core Data bei Habré, Multithreading wird jedoch nur unzureichend behandelt, und meines Erachtens haben sich fast alle die Frage gestellt, wie es richtig implementiert werden soll.

Allgemeine Bestimmungen


Kurz gesagt, der Core Data Stack besteht aus mehreren Hauptteilen.



1) NSPersistentStore, bei dem es sich um eine Binärdatei, eine XML- oder eine SQLite-Datei handeln kann.
2) NSManagedObjectModel, eine kompilierte Binärversion des Datenmodells.
3) NSPersistentStoreCoordinator lädt Daten aus NSPersistentStore und NSManagedObjectModel, speichert und speichert sie zwischen.
4) NSManagedObjectContext, Laden von Daten aus NSPersistentStore in den Speicher, Betrieb mit Instanzen.
5) NSManagedObject - Objekt des Datenmodells.

Das meiner Meinung nach unangenehme Hauptmerkmal dieses ganzen Wunders ist, dass NSManagedObjectContext nicht threadsicher ist.

Stack-Initialisierung


Bei großen Datenbankgrößen kann die Initialisierung des Stapels auf dem Hauptthread während der Migration mehr als 30 Sekunden dauern. Dies führt dazu, dass das System die Anwendung einfach beendet. Es gibt einen Ausweg, den Stapel in einem anderen Thread zu initialisieren.

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // Создание NSManagedObjectModel
        NSURL *modelURL = [[NSBundle mainBundle] URLForResource:kModelFileName withExtension:@"momd"];
        NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
        // Создаем NSPersistentStoreCoordinator
        NSPersistentStoreCoordinator *psc =  [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
        // Добавляем к NSPersistentStoreCoordinator хранилище, именно на этой операции приложение может висеть очень долго
        NSURL *doсURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
                                                                     inDomains:NSUserDomainMask] lastObject];
        NSURL *storeURL = [doсURL URLByAppendingPathComponent:@"CoreData.sqlite"];
        NSError *error = nil;
        NSPersistentStore *store =  [psc addPersistentStoreWithType:NSSQLiteStoreType
                                  configuration:nil
                                            URL:storeURL
                                        options:nil
                                          error:&error];
       // Создание контекстов
});


So wurde unsere Anwendung gestartet, der Benutzer erhielt keine Verzögerungen in der Benutzeroberfläche, jeder ist glücklich. Wir folgen weiter.

Hauptkontexte erstellen


Wie ich oben schrieb, ist NSManagedObjectContext nicht threadsicher. Daher ist es angemessen, den Hauptanwendungskontext im Hauptthread beizubehalten. In diesem Fall wird die Benutzeroberfläche jedoch verlangsamt, während dieser Kontext beibehalten wird. Was soll ich tun? In iOS 6 wurden die NSManagedObjectContext-Typen angezeigt.

1) NSMainQueueConcurrencyType - exklusiv im Hauptthread verfügbar.
2) NSPrivateQueueConcurrencyType - wird in einem Hintergrundthread ausgeführt.
3) NSConfinementConcurrencyType - wird auf dem Thread ausgeführt, auf dem es erstellt wurde.

Und auch die Möglichkeit, Kinderkontexte zu schaffen. Beim Speichern werden alle Änderungen des untergeordneten Kontexts an das übergeordnete Element übertragen. Dementsprechend besteht jetzt die Möglichkeit, Ihren Manager wie folgt für die Arbeit mit CoreData auszurüsten.

// Этот код нужно вызывать после инициализации стека в том же потоке. 
// _daddyManagedObjectContext является настоящим отцом всех дочерних контекстов юзер кода, он приватен.
_daddyManagedObjectContext = [[NSManagedObjectContext alloc]  initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [_daddyManagedObjectContext setPersistentStoreCoordinator:psc];
    // Далее в главном потоке инициализируем main-thread context, он будет доступен пользователям
    dispatch_async(dispatch_get_main_queue(), ^{
        _defaultManagedObjectContext = [[NSManagedObjectContext alloc]  initWithConcurrencyType:NSMainQueueConcurrencyType];
        // Добавляем наш приватный контекст отцом, чтобы дочка смогла пушить все изменения
        [_defaultManagedObjectContext setParentContext:_daddyManagedObjectContext];
    });


An diesem Punkt endet die Initialisierung unseres Managers für die Arbeit mit Core Data. Jetzt haben wir einen Vater-Kontext, der vor neugierigen Blicken verborgen ist, sowie eine Tochter im Haupt-Thread.

Kinderkontexte erstellen


Wie Sie leicht erraten können, können wir bei der Arbeit an Hintergrundabläufen unsere Kontexte erstellen, indem Sie einfach unseren oben erstellten untergeordneten Kontext als Vorfahren hinzufügen.

- (NSManagedObjectContext *)getContextForBGTask {
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [context setParentContext:_defaultManagedObjectContext];
    return context;
}


In diesem Kontext werden die Änderungen beim Speichern immer im übergeordneten Element gespeichert. Somit haben wir immer die relevantesten Informationen in _defaultManagedObjectContext (wodurch Änderungen an das echte übergeordnete Element übertragen werden).

Kontexte speichern


Das Wichtigste, was übrig bleibt, ist die Erhaltung. Auf Kontexte, die von Hintergrundstreams leben, kann nur über performBlock: und performBlockAndWait: zugegriffen werden. Daher sieht das Speichern des Hintergrundstreams folgendermaßen aus.

- (void)saveContextForBGTask:(NSManagedObjectContext *)bgTaskContext {
    if (bgTaskContext.hasChanges) {
        [bgTaskContext performBlockAndWait:^{
            NSError *error = nil;
            [backgroundTaskContext save:&error];
        }];
       // Save default context
    }
}


Nachdem Sie den untergeordneten Kontext gespeichert haben, müssen Sie den übergeordneten Kontext speichern.

- (void)saveDefaultContext:(BOOL)wait {
    if (_defaultManagedObjectContext.hasChanges) {
        [_defaultManagedObjectContext performBlockAndWait:^{
            NSError *error = nil;
            [_defaultManagedObjectContext save:&error];
        }];
    }
    // А после сохранения _defaultManagedObjectContext необходимо сохранить его родителя, то есть _daddyManagedObjectContext
    void (^saveDaddyContext) (void) = ^{
        NSError *error = nil;
        [_daddyManagedObjectContext save:&error];
    };
    if ([_daddyManagedObjectContext hasChanges]) {
        if (wait)
            [_daddyManagedObjectContext performBlockAndWait:saveDaddyContext];
        else 
            [_daddyManagedObjectContext performBlock:saveDaddyContext];
    }
}


Fazit


Seit einigen Jahren höre ich oft von Entwicklern, dass Core Data eine große Anzahl von Minuspunkten hat. Daher wird die Wahl beispielsweise für FMDB getroffen , das Hauptargument ist Multithreading oder angeblich das Fehlen von Core Data. Der Zweck des Artikels ist genau, diesen Mythos zu zerstreuen.
Es wurden viele Frameworks für die Arbeit mit Core Data geschrieben. Das wichtigste ist meiner Meinung nach MagicalRecord . Beinhaltet eine Vielzahl von Funktionen. Es ist erwähnenswert, dass es in etwa nach der oben beschriebenen Methode funktioniert. Jedes Framework muss richtig angewendet werden, was bedeutet, zu verstehen, wie es funktioniert.

Das ist alles Vielen Dank für Ihre Aufmerksamkeit.

Jetzt auch beliebt: