Core Data Multithreading mit MagicalRecord

Nach einigen Abstürzen aufgrund von Zugriffen auf einen NSManagedObjectContext (Kontext) durch mehrere Threads haben wir nach einer Lösung gesucht, die unseren Code robuster macht. Das durch nutzlose Fehlermeldungen mühsame und zeitaufwändige Debugging solcher Fehler wollten wir unbedingt vermeiden.

Die Grundregeln bei dem Umgang mit Core Data Multithreading lesen sich einfach:

  • Jeder Kontext darf nur von dem Thread aus verwendet werden, auf dem er erstellt wurde.
  • NSManagedObjects (Objekte) sind fest an ihren Kontext gebunden. Nur ihre IDs dürfen Kontext- und damit Threadgrenzen passieren.

Unser Projekt enthält zahlreiche NSOperations (Operationen), die vom System auf einem Hintergrund-Thread ausgeführt werden.  Zur Einhaltung der oben genannten Regeln wird am Anfang jeder Operation ein neuer Kontext erstellt. Dieser Kontext wird nun über mehrere Hierarchien an Hilfsobjekte weitergereicht, um beispielsweise Objekte über NSFetchRequests zu holen.

Folgende Probleme ergeben sich dadurch:

  1. Die Signaturen vieler Methoden werden durch einen Kontext-Parameter aufgebläht.
  2. Eine Hilfsmethode, die intern den Standard-Kontext verwendet, wird von einem zweiten Entwickler in gutem Glauben aus einer Operation heraus aufgerufen.
  3. Die Übergabe von nil als Kontext an Hilfsmethoden ist zum Synonym für den Main-Thread-Kontext (Standard-Kontext) geworden. Dieser Automatismus kann aber für Fehler sorgen, wenn man vergisst den korrekten Kontext mitzugeben.

 

Magical Record

Offensichtlich gibt es einen engen Zusammenhang zwischen Kontext und Thread. Also warum wird der Kontext nicht einfach an den Thread gebunden und passend zum aktuellen Thread automatisch gewählt. Mit diesem Gedanken stößt man bei der Recherche auf Github schnell auf MagicalRecord.

Inspiriert von der Active Record Implementierung in Ruby on Rails bietet MagicalRecord zum einen Methoden zum Importieren von Daten und zum anderen die kompakte Erstellung von Fetch-Requests wie im folgenden Beispiel:

[User findFirstByAttribute:@“phone“ withValue:@“012“]

Ohne MagicalRecord wäre die Erstellung des gleichen Fetch-Requests mindestens fünf Zeilen lang.

Unterliegend verwendet MagicalRecord sogenannte Nested-Contexts, die Apple mit iOS 5 eingeführt hat. Dabei hat ein Kontext keinen NSPersistentStore zum Speichern, sondern einen Eltern-Kontext. Beim Aufruf von „save:“ werden die Änderungen in den Eltern-Kontext und nicht direkt in den NSPersistentStore geschrieben.

Das Einbinden von MagicalRecord gestaltete sich in unserem Fall unter iOS 6 erstaunlich einfach. Nach der Initialisierung des defaultContext mit Hilfe des eigenen NSPersistentStoreCoordinators, erstellt MagicalRecord alle weiteren Kontexte automatisch.

Diese bekommt man sehr einfach mit der statischen Methode contextForCurrentThread der NSManagedObjectContext-Klasse geliefert. Gibt es für den aktuellen Thread keinen Kontext, wird einer erstellt.

In unserem Projekt haben wir nun alle Zugriffe auf den Standard-Kontext durch den aktuellen Thread-Kontext ersetzt. Beim Zugriff vom Main-Thread entspricht dies dem defaultContext.

Der erstellte Thread-Kontext hat automatisch den defaultContext als Eltern-Kontext. Dies entspricht in etwa dem häufigen Fall (ohne Nested-Contexts), dass der Standard-Kontext den Thread-Kontext observiert und alle Änderungen abspeichert.

Es hat sich bewährt, am Anfang und am Ende jeder Operation den contextForCurrentThread mit reset zurück zu setzen, damit nicht versehentlich alte Änderungen im Kontext verbleiben, denn der contextForCurrentThread wird wiederverwendet, solange der Thread nicht vom System abgeräumt wurde.

Zum Speichern bietet MagicalRecord verschiedene Methoden an. Bei Umstellung von einem bestehenden Projekt ohne Nested-Contexts sollte man alle Kontext save: Methoden durch saveToPersistentStoreAndWait ersetzen, da bei beiden Methoden alle späteren Aufrufe davon ausgehen können, dass alle Daten in den NSPersistentStore gespeichert wurden.

Will man den aktuellen Thread nicht blockieren, so kann man auch asynchron mit saveToPersistentStoreWithCompletion: speichern. Aufwändiger wird es, wenn man bisher den Erfolg der save: Methode ausgewertet hat. In diesem Fall muss man alles was nach dem Speichern passieren soll manuell in den completionBlock von saveToPersistentStoreWithCompletion: verlegen.


[context saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
      if (success) ...
}];

 

Ergebnis iOS 6

Ein Abgleich des contextForCurrentThread mit unseren manuell durchgereichten Kontexten hat einige Fehler in unserem alten Code aufgespürt. Alle drei am Anfang aufgezählten Probleme sind aufgetreten und durch threadgebundene Kontexte gelöst worden.

Man muss natürlich weiterhin selbst darauf achten, dass man NSManagedObjects nicht über Threadgrenzen hinweg verwendet.

Will man explizit einen anderen Kontext verwenden, so kann man eine Version des persistierten Objekts durch die Methode inContext: im gewünschten Kontext bekommen.

[[Customer findFirst] inContext:differentContext]

 

Desaster auf iOS 5

Nach den Umstellungen lief die App unter iOS 6 problemlos. Ein Test auf iOS 5 war dagegen ein Desaster. Die App ist nicht einmal mehr gestartet.

Innerhalb von performFetch: eines NSFetchedResultsControllers ist es reproduzierbar zu Deadlocks gekommen. Sinnvolle Fehlermeldungen gab es nicht. Aber das war nicht das einzige Problem: Nested-Contexts auf iOS 5 führen zu Deadlocks, fehlerhaften Suchergebnissen, falschen Sortierungen und Abstürzen.

Eines wird daraus klar: Nested-Contexts wurden zwar unter iOS 5 eingeführt, aber sind so fehleranfällig, dass man sie erst ab iOS 6 in einer produktiven Umgebung einsetzen sollte. Apple hat an dieser Stelle leider geschlafen.

Dieser Blog liefert eine Beschreibung der oben genannten Probleme.

 

Magical Record ohne Nested-Contexts

Da wir MagicalRecords Fetching, Logging, ErrorHandling, etc. weiter verwenden wollten, haben wir alle Abhängigkeiten zu Nested-Contexts aus dem Framework entfernt und lassen den defaultContext andere Kontexte observieren.

Das Erstellen von Kontexten, Speichern und alle blockbasierten Methoden mussten dazu angepasst oder entfernt werden.

 

Automatische Tests

Beim automatischen Testen eines Projekts ersetzen wir vor jedem Test MagicalRecords defaultContext durch einen neuen Kontext der einen NSInMemoryStore verwendet. Da alle anderen Kontexte vom defaultContext abgeleitet werden und daher den selben NSPersistentStore verwenden, muss man ansonsten nichts weiter beachten.

 

Fazit

Trotz der wichtigen Lehre, dass Nested-Contexts unter iOS 5 unbrauchbar sind und des damit verbundenen zusätzlichen Aufwands hat sich die Arbeit gelohnt. Dank MagicalRecord und etwas Handarbeit ist der Code jetzt zentralisierter, kürzer und robuster geworden. Kontexte werden passend zum Thread automatisch gewählt, Fehler werden vermieden, Abstürze wurden beseitigt und als Entwickler kann man wieder ruhig schlafen.

Neuromarketing Kongress 2013 – Antworten des Marketings auf die Zukunftstrends

Neuromarketing Kongress 2013 - Antworten des Marketings auf die Zukunftstrends

Am 26. April fand in der BMW World in München der nun schon sechste Neuromarketing Kongress des Haufe-Verlags und Gruppe Nymphenburg statt. Thematisiert wurden in diesem Jahr die aktuellen Marketing-Trends Age-, Gender, Trust- und Global-Marketing in Anlehnung an das Neuromarketing. … Weiterlesen

Recommendersysteme (Teil 2) – Erwartungen, Potenzial, Schwächen, Stärken.

Wie bereits in Teil 1 beschrieben, sollen Recommendersysteme dem Anwender helfen, aus einer grossen Menge an Informationen oder Produkten das herauszufiltern, was für ihn individuell von Nutzen ist. Genau dieser individuelle Nutzen ist jedoch nur schwer zu messen und nicht immer im gewünschten Ausmass zu erreichen.

(mehr …)

B2B Content Marketing: Ein Beispiel aus der Schweiz

Ein Mitarbeiterblog wie derjenige von Zühlke demonstriert die Expertise des Unternehmens mit hochwertigem Content.

Wir lieben Content Marketing und sind davon überzeugt, dass Content Marketing und B2B besonders gut zusammenpassen. Es freut uns deshalb, wenn wir auf neue Beispiele stossen, die dies vor Augen führen. Noch mehr freuen wir uns, wenn wir unsere Kunden … Weiterlesen

Recommendersysteme (Teil 1) – Ein paar Basics

Recommendersysteme – obwohl fast jeder von uns schonmal (unwissentlich) über ein Recommendersystem gestolpert ist, ruft der Begriff oftmals noch ein grosses Fragezeichen auf einzelnen Gesichtern hervor. Eine ganz einfache Erklärung lautet dann: “z.B. die Bücherliste, die mir bei Amazon empfohlen wird. Vielleicht kennst du auch Netflix, Last.fm oder Pandora?“ (mehr …)

Facebook für Leute die Daten mögen (so wie Stephen Wolfram)

friend-ages-vs-age

Was die Faktenrechenmaschine Wolfram Alpha mit seinem eigenen Facebook-Profil anstellen kann, hatte ich bereits beschrieben. Nach über eine Million von Analysen sinniert Stephen Wolfram in seinem Blog über das summarische Datenset. Im Überblick zeigt die Analyse, die sich vor allem … Weiterlesen

Dirty Tricks mit Axure: Responsive.

Axure

Axure ist nach wie vor die erste Wahl beim Entwickeln von Prototypen fürs Web. Auch wenn die Integration dynamischer Funktionen die Graubärtigen unter uns an vielen Stellen noch an Flash 3 erinnert: Axure bietet jede Menge schöner Möglichkeiten, Web-Konzepte mit … Weiterlesen

Corporate Language: Mit konsequenter Tonalität zu den Sternen.

Per Anhalter durch die Galaxis - Lapke - Namics

Um in den unendlichen Weiten des Internets zu strahlen wie die Sonnen von Soulianis und Rahm, hebt qualitativ hochwertiger Content ein Unternehmen hervorragend von der Konkurrenz ab. Doch damit Content funktioniert, kommt es auf eine konstante, zielgruppen- und kanalspezifische Ansprache an. Hier helfen die Sprachklimazonen. Weiterlesen

Seite 20 von 204« Erste...10...1819202122...304050...Letzte »