Objektpersistenz mit Core Data
Core Data ist ein Framework für die persistente Speicherung von Objekten gemäß eines Datenmodells. Dabei werden bidirektionale 1:n und n:m Beziehungen zwischen Objekten unterstützt. Das Datenmodell wird dabei in einer .xcdatamodel-Datei im App-Projekt abgelegt:
Core Data Klassen
Für die Verwendung von Core Data werden folgende Objekte benötigt:
-
Das NSManagedObjectModel lädt und repräsentiert das im xcdatamodel definierte Datenmodell zur Laufzeit.
-
Der NSPersistentStoreCoordinator verwaltet die Persistierung der Daten mittels einem NSPersistentStore für SQLite, Binary-Archiven oder In-Memory. Typischerweise wird ein SQLite-Persistent Store mit Speicherung im Dokumentenverzeichnis der Anwendung verwendet.
-
Die Datenobjekte, die als NSManagedObject repräsentiert werden, werden zur Laufzeit in einem NSManagedObjectContext verwaltet:
Beispielcode für die Konfiguration dieser Objekte finden Sie, wenn Sie ein neues Xcode-Projekt mit File » New Project » Empty Application erstellen und die Option „Use Core Data“ aktivieren.
Entitätsklassen für Core Data
Mit „Editor » Create NSManagedObject subclass“ können in Xcode aus dem Datenmodell Swift-Klassen generiert werden. Diese erben von NSManagedObject und deklarieren ihre Eigenschaften als @NSManaged. Da der Name der Entität im Code häufig benötigt wird, empfiehlt es sich, dafür eine Konstante anzulegen:
import CoreData
class Deck : NSManagedObject {
static let entityName = "Deck"
@NSManaged var name : String?
}
Objekte anlegen
Core-Data-Objekte dürfen nicht direkt konstruiert werden, sondern müssen über die NSEntityDescription erstellt werden und so dem NSManagedObjectContext bekannt gemacht werden:
let person = NSEntityDescription.insertNewObjectForEntityForName(Deck.entityName,
inManagedObjectContext:managedObjectContext)
Objekte abfragen
Abfragen werden mit einem NSFetchRequest gestellt. Hier können mit den Eigenschaften predicate und sortDescriptors Filterbedingungen und Sortierreihenfolge angegeben werden:
let fetchRequest = NSFetchRequest(entityName: Deck.entityName)
fetchRequest.predicate = NSPredicate(format: "name == %@", name)
fetchRequest.sortDescriptors = [ NSSortDescriptor(key:"name", ascending:true) ]
let results = try! managedObjectContext.executeFetchRequest(fetchRequest)
Objekte löschen
Objekte werden über den NSManagedObjectContext gelöscht:
managedObjectContext.deleteObject(entity)
Schema-Migration
Für Core Data-Modelldefinitionen können mehrere Versionen gleichzeitig im Projekt konfiguriert werden, mit Editor » Add Model Version werden neue Versionen hinzugefügt.
Wird der Persistent Store mit folgenden Optionen konfiguriert, werden die Modell-Versionen automatisch verglichen und daraus Schritte zur Migration der Datenbank auf die aktuelle Version abgeleitet:
let options = [
NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true
]
Dabei werden hinzugefügte und gelöschte Felder automatisch behandelt. Für umbenannte Felder kann der alte Name als Renaming ID eingetragen werden:
Tutorial: Persistenz mit Core Data implementieren
-
Laden Sie den Start-Stand von dem Flashcards-Beispielprojekt: Flashcards.zip
-
Erstellen Sie unter Model mit File » New » iOS » Core Data » Data Model ein Datenmodell Flashcards.xcdatamodeld:
-
Öffnen Sie das Datenmodell und fügen Sie mit Add Entity die Entität Card hinzu. Legen Sie für Card die Eigenschaften frontText und backText an und wählen Sie als Typ String aus:
-
Im Folgenden werden im Projekt Klassen für Card generiert. Seit Xcode 8.1 generiert Xcode die Klassen jedoch zusätzlich automatisch beim Build. Deaktivieren Sie dies, indem Sie die Entität Card auswählen und Manual/None für Codegen auswählen.
-
Wählen Sie „Editor » Create NSManagedObject subclass“, um Swift-Klassen für das erstellte Datenmodell zu generieren:
Wählen Sie das Data Model Flashcards und die Entität Card aus und konfigurieren Sie im Dateiauswahldialog, dass Swift-Klassen in der Model-Gruppe generiert werden sollen:
-
Löschen Sie die alte Modellklasse Card.swift.
-
Deklarieren Sie in Card+CoreDataClass.swift eine Konstante entityName für den Namen der Core-Data-Entität:
public class Card: NSManagedObject { static let entityName = "Card" }
-
Fügen Sie dem Xcode-Projekt unter Helper die Extension NSPersistentContainer+Defaults.swift hinzu. Diese vereinfacht das Erstellen der für Core Data benötigten Objekte.
-
Erstellen Sie eine Klasse FlashcardsModel, die ein Core-Data-managedObjectContext für die App erzeugt:
import Foundation import CoreData class FlashcardsModel { private let persistentContainer : NSPersistentContainer init() { self.persistentContainer = NSPersistentContainer(defaultContainerWithName: "Flashcards") } var viewContext : NSManagedObjectContext { return self.persistentContainer.viewContext } }
-
Deklarieren Sie eine berechnete in FlashcardsModel die alle Lernkarten mit einem NSFetchRequest lädt:
var cards : [Card] { let request : NSFetchRequest<Card> = Card.fetchRequest() return try! viewContext.fetch(request) }
-
Erstellen Sie eine Methode createCard in FlashcardsModel, die ein Card-Objekt mittels NSEntityDescription.insertNewObjectForEntityForName in dem managedObjectContext erzeugt:
func createCard() -> Card { let card = NSEntityDescription.insertNewObject(forEntityName: Card.entityName, into: self.viewContext) as! Card return card }
-
Deklarieren Sie im FlashcardsModel eine Methode save, die die Persistierung der Änderungen an dem managedObjectContext auslöst:
func save() { assert(Thread.isMainThread) do { try self.viewContext.save() } catch let error { NSLog("%@", "An error occurred when saving: \(error)") } }
-
Passen Sie den StartViewController so an, dass beim Download von den Lernkarten über das FlashcardsModel Card-Entitäten angelegt werden:
class StartViewController: UIViewController, DownloadsTableViewControllerDelegate { // ... // MARK: - DownloadsTableViewControllerDelegate func downloadFinished(terms: [Term]) { for term in terms { let card = FlashcardsModel.shared.createCard() card.frontText = term.term card.backText = term.definition } FlashcardsModel.shared.save() updateView() } }
-
Starten Sie die App und prüfen Sie, dass in die lokale Bibliothek übernommene Kartenstapel auch nach einem Neustart der App vorhanden sind.
-
Aktivieren Sie die automatische Code-Generierung, indem Sie in Flashcards.xcdatamodeld die Eigenschaft Codegen für die Card-Entität auf Category/Extension einstellen. Löschen Sie die im Projekt generierten Datei Card+CoreDataProperties.swift - diese werden nun automatisch beim Build erzeugt.
-
Setzen Sie die App in den Ursprungszustand zurück, indem Sie im Simulator iOS Simulator » Reset Content and Settings wählen oder mit Hardware » Home und langem Tap auf das App-Icon die App vom Simulator löschen.
-
Committen Sie die Änderungen: „Objektpersistenz mit Core Data“.
Core Data SQLite-Datenbank inspizieren
-
Führen Sie die App im Simulator aus und kopieren Sie den Pfad zur sqlite3-Datenbankdatei, der in der Console ausgegeben wird:
und verwenden Sie das Terminal, um die Datenbank mit dem sqlite3-Kommando zu öffnen:
sqlite3 /Users/bob/Library/Developer/CoreSimulator/.../Flashcards.db
Verwenden Sie folgende Kommandos um die Datenbankinhalte anzuzeigen:
sqlite> .help ... sqlite> .tables ZCARD Z_METADATA Z_MODELCACHE Z_PRIMARYKEY sqlite> .mode column sqlite> .headers on sqlite> select * from ZCARD; Z_PK Z_ENT Z_OPT ZBACKTEXT ZFRONTTEXT ---------- ---------- ---------- ---------- ---------- 1 1 1 Vogel bird 2 1 1 Baum tree 3 1 1 Haus house
-
Beenden Sie sqlite3 mit .quit.
Migration bei Schema-Änderungen
-
Öffnen Sie Flashcards.xcdatamodeld und wählen Sie im Menü Editor » Add Model Version... um eine Modell-Version Flashcards 2 zu erstellen:
-
Fügen Sie Card ein Attribut scheduleDate vom Typ Date hinzu:
-
Wählen Sie Flashcards 2 als aktive Model Version aus:
-
Starten Sie die App testweise - die bestehende Datenbank wird automatisch migriert.
-
Committen Sie die Änderungen als „Card.scheduleDate per Model Version hinzugefügt“.
Weitere Informationen
-
Core Data FrameworkDokumentation zu Core Data.
-
Core Data Programming GuideDokumentation zu Core Data.
-
Predicate Programming GuideDokumentation der Abfrage-Möglichkeiten mit NSPredicate.
-
Core Data & ConcurrencyHinweise zur Thread-Sicherheit und nebenläufiger Programmierung mit Core Data.