- oder 10 im Kampf CoreData-Lektionen gelernt
- CoreData ist ein Framework für die Verwaltung von Objektgraphen
- CoreData kann die Daten in verschiedenen Formaten speichern
- Ein paar Begriffsdefinitionen
- Verwendung von NSPersistentContainer
- Beziehungen verwenden
- Vermeiden Sie so viel wie möglich stringly Code
- Halten Sie Ihren NSPersitentContainer.viewContext schreibgeschützt
- Überwachen Sie diese Threads
- Verwenden Sie das Start-Argument, um den Kontext und das Threading zu verfolgen
- Verwenden Sie Aggregatabfragen
- Kerndatenbezogenen Code in Kategorien aufbewahren
- Wählen Sie ein Transaktionsmodell für Ihre Datenänderungen
- Speichern Sie den Kontext nicht aus einer Modellkategorie-Methode heraus
oder 10 im Kampf CoreData-Lektionen gelernt
Ich habe CoreData über mehrere Projekte während meiner Apple-Plattform-Entwickler-Karriere verwendet (das erste Mal war in MacOS 10.5…), und im Laufe der Jahre habe ich einiges auf die harte Tour gelernt (aka: „meinen Kopf viel zu lange gegen die Wand schlagen“). Also dachte ich mir, ich teile mein Wissen mit Ihnen. Hoffentlich erspare ich Ihnen ein paar blaue Flecken auf der Stirn.
Bitte beachten Sie, dass dies kein Leitfaden für die Einrichtung von CoreData ist. Es soll ein Artikel mit einigen guten Praktiken sein, die Sie für Ihre Projekte übernehmen können.
Hier ist Apples kurzer Überblick: „Verwenden Sie Core Data, um die permanenten Daten Ihrer Anwendung für die Offline-Nutzung zu speichern, um temporäre Daten zwischenzuspeichern und um Ihrer App auf einem einzigen Gerät eine Undo-Funktionalität hinzuzufügen.“
Um ein wenig mehr ins Detail zu gehen, ist CoreData Apples Technologie, um Ihre strukturierten Daten lokal zu speichern. Sie verwenden den Modelleditor, um Ihre Datenstruktur zu definieren, genau wie einen Datenbankschema-Editor, und dann können Sie diese Objekte in Ihrem Code verwenden, um Ihre Daten zu speichern.
CoreData ist ein Framework für die Verwaltung von Objektgraphen
Das bedeutet, dass Sie bei der Erstellung Ihres Modells Ihre Objekte und deren Beziehungen definieren. Wenn Sie zum Beispiel eine Entität „Firma“ und eine Entität „Mitarbeiter“ in Ihrem Datenmodell haben, werden diese beiden Entitäten durch eine Beziehung verknüpft, die diese beiden miteinander verbindet. Die Entität Company
hat wahrscheinlich den Namen employees
und die Entität Employee
den Namen employer
. Sie fügen einfach die Beziehungseigenschaft zu den Entitäten hinzu und das war’s. Sie brauchen keine Join-Tabelle wie in einer Datenbank zu erstellen.
Das Schöne an CoreData im Vergleich zu einer herkömmlichen Datenbank ist, dass Sie bei einer Abfrage nach Unternehmen X die Mitarbeiter direkt abrufen können, indem Sie einfach auf company.employees
zugreifen. Sie müssen keine Join-Abfrage erstellen oder erneut abfragen.
CoreData kann die Daten in verschiedenen Formaten speichern
Ja, Sie haben richtig gehört, CoreData kann Ihre Daten in verschiedenen Formaten/Orten speichern. Jedes hat seine Vor- und Nachteile.
- Binärspeicher
- SQLite-Speicher
- In-Memory-Speicher
Sie können diesen Artikel lesen, um die Vor- und Nachteile jedes Formats zu erfahren. Ich benutze immer SQLite, aber ich könnte mir vorstellen, dass ein In-Memory-Speicher superschnell ist.
Ein paar Begriffsdefinitionen
NSManagedObject
: Dies ist die Basisklasse, die die Objekte repräsentiert, die Sie in CoreData gespeichert haben.
Abrufanfrage: Dies ist das Äquivalent einer Abfrage in der Welt von CoreData. Sie wird durch die Klasse NSFetchedObject
repräsentiert.
Verwendung von NSPersistentContainer
Bis iOS 10.0 war das Einrichten des CoreData-Stacks mit einer ganzen Menge Boilerplate-Code verbunden. Man musste sich selbst darum kümmern, die Strategie für die Verwendung von CoreData in mehreren Threads zu definieren.
Mit NSPersistentContainer
kapselt Apple den Großteil des Boilerplate-Codes. Das erspart Ihnen die Diskussion darüber, wie Sie Ihren Stack am besten einrichten, um eine optimale Leistung zu erzielen.
Dieser Artikel enthält alle Informationen, die Sie für die korrekte Verwendung benötigen.
Beziehungen verwenden
Ich erwähne dies immer, weil bei zwei Gelegenheiten, für Projekte, die ich geerbt habe, das CoreData-Modell wie eine Datenbank eingerichtet war. Es wurden keine Beziehungen definiert. Stattdessen wurde die ID des zugehörigen Objekts gespeichert, so dass eine zusätzliche Abrufanforderung erforderlich war, um das zugehörige Objekt zu erhalten. Ich glaube, viele Entwickler gehen an CoreData wie an eine Datenbank heran und begehen diesen Fehler. Das macht das ganze Projekt viel komplexer und langsamer.
Folgen Sie diesem Artikel, um den Einstieg zu finden.
Vermeiden Sie so viel wie möglich stringly Code
Stringly Code bezieht sich auf Code, der Strings für den Zugriff auf Daten verwendet. Unter iOS gibt es ein paar String-APIs: UserDefaults
, CoreData
sind einige, die mir in den Sinn kommen.
Wenn wir auf das Beispiel mit der Firma zurückkommen, gibt es zwei Optionen, um eine Firma zu erstellen und ihren Namen festzulegen:
//stringly version to create and edit a company
let company = NSEntityDescription.insertNewObject(forEntityName: "Company", in: managedObjectContext)
company.set("my less than great corporation", forKey: "name")//non stringly version
let company = Company(in: managedObjectContext)
company.name = "My super corporation"
Auf den ersten Blick funktionieren beide dieser Optionen, sie kompilieren, es gibt nichts Falsches an ihnen…
Das Problem mit Stringly-Code ist, dass der Compiler Ihnen nicht helfen kann. Stellen Sie sich vor, dass Sie auf die name
der Firma an mehreren Stellen im Code zugreifen müssen, und realistischerweise brauchen Sie mindestens zwei: eine, um die zu setzen und eine, um sie zu lesen, müssen Sie jetzt diese name
Zeichenkette an zwei Stellen eingeben. Natürlich kann man eine Variable erstellen, um die Zeichenkette zu speichern und diese Variable zu verwenden, aber das erfordert zusätzlichen Aufwand, die Eingabe wird nicht überprüft und wir alle wissen, dass Entwickler faul sind…
So bleibt die Zeichenkette normalerweise an mehreren Stellen. Dann will man sie umbenennen und muss sie mit Suchen und Ersetzen aufspüren… Wenn man eine übersieht, stürzt die App ab.
Hier ist die Liste der Compiler-Features, von denen man mit der stringly-Syntax nicht profitieren kann:
- auto-complete
- refactor
- compiler warning in case of mispelling
- compiler error if you remove/renamed this entity or property in your model
Um die nicht-stringly Version des Codes verwenden zu könnenstringly Version des Codes verwenden zu können, müssen Sie einfach den Compiler bitten, Klassen für Ihre Entitäten zu generieren (Sie können Apples Anweisungen unter „Wählen Sie eine Codegenerierungsoption“ befolgen). Dies ist jetzt die Standardeinstellung, wenn Sie eine neue Entität erstellen.
Halten Sie Ihren NSPersitentContainer.viewContext schreibgeschützt
Aus Leistungsgründen möchten Sie keine langen Operationen auf dem viewContext durchführen, da diese auf dem Haupt-Thread ausgeführt werden und möglicherweise die Benutzeroberfläche blockieren.
Die Lösung ist, sich anzugewöhnen, Änderungen an Ihren Objekten nur innerhalb eines Blocks vorzunehmen, den Sie mit der Methode NSPersistentContainer.performBackgroundTask()
planen. Wenn Sie Ihre Änderungen in diesem Block speichern, werden diese wieder in NSPersitentContainer.viewContext
zusammengeführt und Sie können Ihre Benutzeroberfläche aktualisieren.
Überwachen Sie diese Threads
Eine der Hauptschwierigkeiten bei CoreData ist die Verwendung auf eine thread-sichere Weise. Wenn man das Konzept einmal verstanden hat, ist es ziemlich einfach.
Es läuft darauf hinaus, dass jedes Datenobjekt, das man von CoreData erhält, nur in dem Thread verwendet werden kann, an den das Objekt managedobjectContext
angehängt ist.
Eine Möglichkeit, um sicherzustellen, dass Sie den richtigen Thread verwenden, um auf Ihr Objekt zuzugreifen, ist die Verwendung von NSManagedObjectContext.performBlockAndWait
, die sicherstellt, dass Ihr Code im richtigen Thread verwendet wird.
Sobald Sie sich in diesem Block befinden, müssen Sie NSManagedObject.objectID
und NSManagedObjectContext.existingObject(withId:)
verwenden, um das Objekt über Threads und den Kontext hinweg weiterzugeben.
Wenn Sie dies nicht tun, werden Sie höchstwahrscheinlich mit Deadlocks enden. Stellen Sie sich vor:
- Sie planen eine Aufgabe vom Hauptthread aus mit In dem Fall, dass etwas vom Hauptthread aus geplant wird
NSManagedObjectContext.performBlockAndWait()
. Zu diesem Zeitpunkt ist der Hauptthread blockiert und wartet darauf, dass dieser Block abgeschlossen wird. - Innerhalb dieses Blocks übergeben Sie das Objekt
foo
aus dem Hauptkontext an die Methodebar()
und senden es an einen anderen Thread. - Um ein guter CoreData-Bürger zu sein, holt sich
bar()
den an foo angehängten Kontext und sendet seine Aufgabe mitfoo.managedObjectContext.performBlockAndWait()
- Bumm, du bist in einer Sackgasse, da dieser Kontext nur auf dem Haupt-Thread ausgeführt wird und der Haupt-Thread auf dessen Fertigstellung wartet.
Eine andere Konvention, die hilft, ist die Übergabe von Objekten zwischen View Controllern nur dann, wenn sie im Kontext des Hauptthreads existieren.
Verwenden Sie das Start-Argument, um den Kontext und das Threading zu verfolgen
Wenn Sie die obigen Tipps befolgen, sollte dies die Wahrscheinlichkeit begrenzen, dass eine NSManagedObject
Instanz im falschen Thread aufgerufen wird. Aber das kann immer noch passieren. Stellen Sie also sicher, dass Sie während der Entwicklung die Thread-Debugging-Assertion einschalten
-com.apple.CoreData.ConcurrencyDebug 1
Dieser Artikel erklärt Ihnen genau, wie Sie das tun, und informiert Sie über andere Flags, die Sie für mehr Debugging einrichten können.
Verwenden Sie Aggregatabfragen
CoreData bietet groupBy
, sum
und count
als Aggregatfunktionen, die während einer Abfrage ausgeführt werden können. Die Einrichtung kann ein wenig kompliziert sein, aber es lohnt sich.
Die Alternative ist, eine Abfrage zu erstellen, um alle Objekte zu erhalten, die Ihren Kriterien entsprechen, und dann eine for-Schleife durch alle Entitäten laufen zu lassen und die Berechnungen selbst durchzuführen. Das ist zwar einfacher zu programmieren, aber viel weniger effizient.
Wenn Sie eine aggregierte Fetch-Anfrage ausführen, übersetzt CoreData diese in eine Datenbankabfrage und führt die Berechnungen direkt auf Datenbankebene durch. Das macht es superschnell.
Dieser Artikel ist eine gute Einführung in die Vorgehensweise.
Kerndatenbezogenen Code in Kategorien aufbewahren
Wenn Sie Ihre NSManagedObject
Unterklasse von Xcode erstellt haben, werden Sie höchstwahrscheinlich feststellen, dass Sie Code haben, der in der Klasse sein sollte, die Ihr Objekt repräsentiert (für die Berechnung von Werten, den Import von Daten von Ihrem Server usw.). Aber Sie haben CoreData gebeten, die Klassen für Sie zu erstellen…
So müssen Sie eine Kategorie für das Objekt erstellen, das zusätzliches Verhalten benötigt. Fügen Sie dort nur Code ein, der mit CoreData zu tun hat, damit Sie ihn sauber halten können.
Wählen Sie ein Transaktionsmodell für Ihre Datenänderungen
Sie können Ihre Objekte so viel ändern, wie Sie wollen, aber um diese Änderungen zu persistieren, müssen Sie NSManagedObjectContext.save()
aufrufen.
Sie werden schnell vergessen, save()
aufzurufen, und Sie haben dann einige baumelnde Änderungen in Ihrem Kontext. Wenn Sie das nächste Mal save()
aufrufen, werden Sie nicht sicher sein, was Sie übertragen.
Verwenden Sie ein Transaktionsmodell, um zu vermeiden, dass Sie einen schmutzigen Kontext zurücklassen. Dazu erstellen Sie eine Kategorie auf NSPersistentContainer
wie folgt:
extension NSPersistentContainer {
@objc func performBackgroundSaveTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
performBackgroundTask { (context: NSManagedObjectContext) in
block(context)
context.save()
}
}
}
Stellen Sie von nun an sicher, dass alle Änderungen, die Sie vornehmen, in einem mit performBackgroundSaveTask()
geplanten Block ausgeführt werden. Auf diese Weise ist gewährleistet, dass die Änderungen gespeichert werden, wenn Sie sie vornehmen.
Speichern Sie den Kontext nicht aus einer Modellkategorie-Methode heraus
Niemals save()
aus einer Entitätskategorie heraus aufrufen. Es gibt nichts Schlimmeres, als eine Methode für Ihr Objekt aufzurufen, ohne zu wissen, dass der gesamte Kontext mitgespeichert wird. Wenn Sie Änderungen vornehmen, die Sie vielleicht nicht speichern wollen. Der Aufruf von save sollte an dem Punkt erfolgen, an dem die Änderungen an dem Objekt begonnen werden.