5.3Core Data
Das Beispielprogramm dieses Kapitels besitzt im Gegensatz zum analogen Wecker eine Modellschicht mit eigenen Klassen. Sie stellen die Daten des Fototagebuchs dar. Die Applikation kann diese Daten indes nicht nur anzeigen und bearbeiten, sondern auch im nichtflüchtigen Speicher des Gerätes ablegen und von dort auch wieder laden. Das Fototagebuch verwendet für die Datenmodellierung und -speicherung das Apple-Framework Core Data.
Core Data ist eine Technologie, um persistente Objektgraphen in Applikationen zu implementieren. Ein Objektgraph besteht aus Objekten, die durch Relationships miteinander verbunden sind, und Persistenz ist die dauerhafte Speicherung von Daten. Jede Applikation lässt natürlich nur bestimmte Objektgraphen zu. Wie die zulässigen Graphen eines Programms aussehen, legt dabei das jeweilige Datenmodell fest.
Solche Datenmodelle lassen sich ebenfalls am einfachsten durch Graphen beschreiben. Vielleicht kennen Sie ja auch schon Entity-Relationship-Diagramme (ER-Diagramme) oder UML-Klassendiagramme, die Ähnliches leisten. Sie stellen die Klassen des Datenmodells als Kästen dar, die durch Linien miteinander verbunden sind. Dabei repräsentieren diese Linien die Relationships des Modells. In Xcode ist ein Datenmodelleditor integriert, der sowohl eine grafische als auch eine dialogorientierte Erstellung und Bearbeitung von Datenmodellen erlaubt. Die grafische Darstellung orientiert sich dabei an ER-Diagrammen.
5.3.1Datenmodellierung

Im Beispielprojekt PhotoDiary finden Sie die Datei PhotoDiary.xcdatamodeld, die das Datenmodell des Fototagebuchs enthält. Wenn Sie die Datei auswählen, öffnet Xcode den Modelleditor (siehe Abbildung 5.4).
Abbildung 5.4 Das Datenmodell in der Dialogdarstellung
In der linken Spalte des Modelleditors finden Sie die Hauptelemente des Datenmodells. Dazu gehören Entitätstypen,
Suchanfragen und Konfigurationen, die Sie über den linken Plus-Button
anlegen können.
Unter Entities finden Sie die Entitätstypen des Datenmodells. Ein Entitätstyp – häufig fälschlicherweise auch nur »Entität« genannt – beschreibt eine Objektklasse
in einem Datenmodell. Er enthält die Beschreibungen für die Attribute und die Relationships
zu den anderen Entitätstypen. Außerdem enthält der Entitätstyp weitere Informationen,
wie beispielsweise die Objective-C-Klasse, die diesen Typ im Programm darstellt, und
er kann analog zu Klassen einen Obertyp haben.
Über Fetched-Propertys können Sie den Objekten des Entitätstyps bestimmte Objekte eines Entitätstyps zuordnen, wobei Sie die Zuordnung über eine Bedingung beschreiben. Wenn beispielsweise ein Datenmodell die Entitätstypen Stadt und Einwohner enthält, dann können Sie alle Millionäre der Stadt über eine Fetched-Property mit der Bedingung »ist ein Einwohner der Stadt und hat ein Vermögen von über 1.000.000 €« modellieren. Über den rechten Plus-Button
legen Sie neue Attribute, Relationships und Fetched-Propertys an.
In der Rubrik Fetch Requests können Sie Vorlagen für Suchanfragen anlegen, mit denen Sie die Entitäten, also Objekte, in dem Objektgraphen wiederfinden können. Außerdem hat jedes Datenmodell eine Konfiguration mit dem Namen »Default«, die alle Entitätstypen enthält. Sie können jedoch weitere Konfigurationen anlegen, die weniger Entitätstypen enthalten. Diese Konfigurationen finden Sie in der Rubrik Configurations.
Durch die Button-Gruppe Outline Style schalten Sie zwischen einer Listenansicht und einer hierarchischen Darstellung auf
die grafische Darstellung des Datenmodells um. In der hierarchischen Darstellung zeigt
Xcode die Subentitätstypen jeweils unterhalb ihrer Superentitätstypen an.
Über die Button-Gruppe Editor Style schalten Sie zwischen der Dialog- und Diagrammdarstellung (siehe Abbildung 5.5) des Modells um. Das Diagramm stellt Entitätstypen durch Kästen und Relationships
durch Linien mit Pfeilenden dar. Die Kästen der Entitätstypen enthalten deren Namen
als Titel, darunter die Namen der Attribute, gefolgt von den Namen der Relationships.
Abbildung 5.5 Diagrammdarstellung des Datenmodells
Im Fototagebuch ist jedem Entitätstyp eine gleichnamige Klasse zugeordnet. Das muss jedoch nicht immer so sein. Die Basisklasse dieser Klassen ist in der Regel die Klasse NSManagedObject, die als Implementierung beliebiger Entitätstypen vollkommen ausreicht.
Das Datenmodell enthält zwei Entitätstypen: »DiaryEntry« und »Medium«. Während der
erste Typ Tagebucheinträge beschreibt, dient der zweite zur Ablage von Mediendaten.
In dem Tagebuch sind das Bilder und Tonaufnahmen. Über den Datenmodellinspektor (+
+
) können Sie den Klassennamen des Entitätstyps festlegen, und über das Dropdown-Menü
Parent Entity legen Sie den Obertyp eines Entitätstyps fest (siehe Abbildung 5.6). Sie haben also in einem Datenmodell auch die Möglichkeit der Vererbung.
Abbildung 5.6 Der Datenmodellinspektor für Entitätstypen
Beide Entitätstypen enthalten einige Attribute, deren Namen und Datentypen Sie in
der Dialogansicht direkt festlegen können. Weitere Einstellungsmöglichkeiten zu jedem
Attribut finden Sie im Datenmodellinspektor des Attributs. Dazu wählen Sie das Attribut
aus, drücken +
+
oder öffnen die Inspektorspalte auf der rechten Seite von Xcode und wählen dort das
Icon ganz rechts aus (siehe Abbildung 5.7).
Bei allen Attributen können Sie drei boolesche Eigenschaften setzen:
- Ein transientes Attribut wird nicht persistent gespeichert. Es geht also spätestens nach dem Programmende verloren.
- Ein optionales Attribut darf auch den Wert nil annehmen. Wenn Sie hingegen versuchen, ein nicht optionales Attribut mit dem Wert nil zu speichern, liefert Core Data einen Fehler.
- Für indizierte Attribute legt Core Data einen Index an, der Suchanfragen beschleunigen kann, die nach diesem Attribut filtern.
Neben der Festlegung des Datentyps erlaubt der Inspektor datentypabhängige Einstellungen für einen Standardwert und Integritätsbedingungen, das sind Einschränkungen für die zulässigen Werte. Bei Strings können Sie beispielsweise eine minimale und maximale Länge sowie einen regulären Ausdruck festlegen, auf den die Werte passen müssen. Core Data erlaubt nur das Speichern von Entitäten, die nicht gegen diese Einschränkungen verstoßen. Sie können also diese Integritätsbedingungen nutzen, um unerlaubte Zustände im Objektgraphen zu verhindern.
Abbildung 5.7 Der Datenmodellinspektor für ein Stringattribut
Im Modell des Fototagebuchs verbinden zwei Relationships die beiden Entitätstypen miteinander, die das Diagramm in Abbildung 5.5 jedoch nur über eine Verbindungslinie darstellt. Das liegt daran, dass die beiden Relationships zueinander invers sind, also die Hin- und Rückrichtung beschreiben. In Core Data sollte möglichst jede Relationship auch immer eine inverse Relationship haben.
Neue Relationships legen Sie in der Diagrammdarstellung analog zu Outlets im Interface Builder an, indem Sie bei gedrückter rechter Maustaste eine Verbindung zwischen den beteiligten Entitätstypen ziehen. Wenn Sie eine Relationship durch das Ziehen einer Verbindung erzeugen, legt der Modelleditor auch automatisch die inverse Relationship an.
Der Entitätstyp »DiaryEntry« verweist über die One-To-Many-Relationship »media« auf die zugehörigen Medien. Ein wichtiges Unterscheidungskriterium für Relationships sind die möglichen Anzahlen der Elemente an beiden Enden. Wenn die Relationship auf ein Element verweist, können Sie sie durch eine Property des Zieltyps darstellen. Ist hingegen mehr als ein Element möglich, müssen Sie dafür einen Sammlungstyp verwenden; in Core Data ist das NSSet oder NSArray. Bei der One-To-Many-Relationship media kann also immer ein Tagebucheintrag auf mehrere Medien verweisen.
Die Many-To-One-Relationship »diaryEntry« weist jedem Medium genau einen Tagebucheintrag zu. Sie ist die inverse Relationship zu »media«. Es gibt außerdem One-To-One- und Many-To-Many-Relationships, die dieses Datenmodell allerdings nicht verwendet. Die Pfeilenden an der verknüpften Entität zeigen die Wertigkeit der Relationships an. To-One-Relationships haben eine einfache Pfeilspitze, und To-Many-Relationships haben eine doppelte Pfeilspitze. Sie können die Wertigkeit über den Datenmodellinspektor für die Relationship setzen (siehe Abbildung 5.8).
Abbildung 5.8 Der Datenmodellinspektor für Relationships
Bei Relationships können Sie neben dem Zielentitätstyp auch die inverse Relationship festlegen. Wie Attribute können Relationships ebenfalls transient und optional sein. Im Inspektor lässt sich allerdings nicht nur festlegen, ob es sich um eine To-One- oder To-Many-Relationship handelt, sondern über das Feld Count auch, auf wie viele Elemente sie mindestens und höchstens verweisen darf. Jeder Tagebucheintrag kann maximal zwei Medien besitzen: jeweils ein Foto und eine Tonaufnahme.
Die Delete Rule legt schließlich fest, was mit dem Objekt bei Löschung des Zielobjekts passiert. Es stehen vier Möglichkeiten zur Auswahl, wie Core Data bei einer Löschung des Ursprungsobjekts mit den Zielobjekten umgehen soll:
- No Action: Core Data verändert nicht die Zielobjekte.
- Nullify: Löscht den Verweis der inversen Relationship auf das Ursprungsobjekt. Core Data setzt also bei To-One-Beziehungen den Verweis auf nil, und bei To-Many-Beziehungen entfernt es das Ursprungsobjekt aus der inversen Relationship.
- Cascade: Löscht auch alle Zielobjekte, auf die das Objekt verweist.
- Deny: Core Data löscht das Objekt nur, wenn keine Zielobjekte existieren.
Die Relationship »media« verwendet kaskadierendes Löschen, weil die Medienobjekte ja zu dem jeweiligen Tagebucheintrag gehören. Wenn der Tagebucheintrag nicht mehr existiert, braucht das Tagebuch auch die Medien nicht mehr. Das Löschen eines Tagebucheintrags bewirkt also automatisch auch das Löschen seiner Medien. Wenn Sie hingegen ein Medium löschen, soll es nur aus der Medienmenge des Tagebucheintrags entfernt werden. Der Tagebucheintrag bleibt allerdings bestehen.
5.3.2Implementierung von Entitätstypen

Das Fototagebuch stellt die Entitätstypen durch eigene Klassen dar, die Xcode direkt aus dem Datenmodell erzeugen kann. Wenn Sie diese Klassen anlegen möchten, wählen Sie im Dialog zum Anlegen einer neuen Datei das Template NSManagedObject subclass unter dem Punkt Core Data aus. Im nächsten Schritt wählen Sie das Core-Data-Modell aus und im darauffolgenden die Entitätstypen, für die Sie die Klassen anlegen möchten.
Die erzeugten Klassen enthalten für jedes Attribut und jede Relationship des Entitätstyps eine entsprechende Property-Deklaration, für die Sie in der Implementierung jeweils eine @synthesize-Anweisung finden:
@dynamic icon;
@dynamic creationTime;
@dynamic updateTime;
@dynamic text;
@dynamic media;
Listing 5.10 Property-Implementierung der Klasse »DiaryEntry«
Im Gegensatz zu @synthesize-Anweisungen haben diese Anweisungen keine Auswirkung auf die Klasse. Sie zeigen dem Compiler lediglich an, dass der Implementierungsteil der Klasse für diese Propertys keine Methodendefinitionen anlegen muss und Sie auch nicht die Synthesize-Anweisung vergessen haben. Stattdessen erfolgt der Zugriff auf die Attributwerte über Key-Value-Coding, wofür die Oberklasse NSManagedObject von DiaryEntry die entsprechenden Implementierungen besitzt.
Außerdem enthält die Headerdatei eine Kategorie mit vier Methoden, über die Sie die Objekte der Relationship »media« verändern können:
@interface DiaryEntry (CoreDataGeneratedAccessors)
- (void)addMediaObject:(Medium *)value;
- (void)removeMediaObject:(Medium *)value;
- (void)addMedia:(NSSet *)value;
- (void)removeMedia:(NSSet *)value;
@end
Listing 5.11 Kategorie mit Methoden
Auch diese Methoden implementiert Core Data über Key-Value-Coding. Da der Tagebucheintrag jedoch noch andere Operationen bei der Veränderung der Relationship ausführen soll, befindet sich diese Kategorie im Beispielprojekt in der Implementierungsdatei der Klasse. Diese Methoden sind also in diesem Projekt privat.
5.3.3Dokumentenordner und Application-Support-Verzeichnis

Core Data speichert die Daten im Dateisystem des Gerätes ab, wobei Sie den Ort dafür festlegen können. Apple empfiehlt dafür zwei Verzeichnisse: den Dokumentenordner und das Application-Support-Verzeichnis.
Jede App unter iOS verfügt über einen Dokumentenordner, in dem die App beliebige Dateien ablegen darf. Wenn Sie über iTunes eine Sicherungskopie der Daten Ihres iOS-Gerätes anlegen, speichert iTunes auch die Dateien in diesen Ordner. Außerdem können Sie diesen Ordner für den Zugriff über iTunes freigeben, indem Sie in der Info.plist die Option UIFileSharingEnabled einschalten, was Sie auch über den Reiter Info in den Target-Einstellungen über den Schlüssel Application supports iTunes file sharing machen können (siehe Abbildung 5.9).
Abbildung 5.9 Dokumentenordner für den Zugriff aus iTunes freischalten
- (NSString *)applicationDocumentsDirectory {
NSArray *thePaths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES);
return thePaths.count == 0 ? nil : thePaths[0];
}
Listing 5.12 Den Dokumentenordner ermitteln
Apple verwendet in vielen Beispielen und den Projektvorlagen den Dokumentenordner für die Ablage der Core-Data-Daten. Wenn eine App dort jedoch auch andere Dateien für den iTunes-Zugriff ablegen will, sollte die App die Daten lieber in einem anderen Verzeichnis speichern. Dazu eignet sich das Application-Support-Verzeichnis, das iTunes in den Sicherungskopien speichert. Im Gegensatz zum Dokumentenordner hat der Nutzer allerdings keinen Zugriff auf dieses Verzeichnis über iTunes.
Apple empfiehlt, die Dateien nicht direkt im Application-Support-Verzeichnis, sondern in einem Unterverzeichnis davon mit dem Namen des Bundle-Identifiers abzulegen. Außerdem müssen Sie beide Verzeichnisse selbst anlegen. Dazu verwendet die Beispielapplikation die Kategorie NSFileManager(StandardDirectories), die auch die Methode aus Listing 5.12 enthält.
Die Methode standardDirectory:inDomain:withSubdirectory:error: ermittelt dazu über die Funktion NSSearchPathForDirectoriesInDomains den Pfad eines Systemverzeichnisses, erweitert es um den angegebenen Pfad für das Unterverzeichnis und legt zu diesem Pfad die entsprechenden Verzeichnisse im Dateisystem über die Methode createDirectoryAtPath:withIntermediateDirectories:attributes:error: der Klasse NSFileManager an.
- (NSString *)standardDirectory:
(NSSearchPathDirectory)inDirectory
inDomain:(NSSearchPathDomainMask)inMask
withSubdirectory:(NSString *)inSubdirectory
error:(NSError **)outError {
NSArray *theDirectories =
NSSearchPathForDirectoriesInDomains(
inDirectory, inMask, YES);
if(theDirectories.count > 0) {
NSString *theDirectory = [theDirectories[0]
stringByAppendingPathComponent:inSubdirectory];
if([self createDirectoryAtPath:theDirectory
withIntermediateDirectories:YES attributes:nil
error:outError]) {
return theDirectory;
}
}
return nil;
}
Listing 5.13 Ermitteln und Erzeugen von Standardverzeichnissen
Damit können Sie nun auf das Application-Support-Verzeichnis inklusive seinem Unterverzeichnis recht bequem zugreifen:
- (NSString *)applicationSupportDirectory {
NSString *theBundleId = [[NSBundle mainBundle]
bundleIdentifier];
NSError *theError = nil;
NSString *theDirectory = [self standardDirectory:
NSApplicationSupportDirectory inDomain:
NSUserDomainMask withSubdirectory:theBundleId
error:&theError];
if(theDirectory == nil) {
NSLog(@"Can't create application support folder: %@",
theError);
}
return theDirectory;
}
Listing 5.14 Zugriff auf das Application-Support-Verzeichnis
Mit Hilfe der Kategorie können Sie sich nun der Core-Data-Einbindung zuwenden.
5.3.4Einbindung von Core Data

Um einen Objektgraphen mit Core Data persistent speichern zu können, müssen Sie drei Objekte anlegen. Das Datenmodell repräsentiert ein Objekt der Klasse NSManagedObjectModel, das das Fototagebuch über einen Lazy-Getter des Application-Delegates aus dem Ressourcenordner der Applikation lädt:
- (NSManagedObjectModel *)managedObjectModel {
if(_managedObjectModel == nil) {
NSURL *theURL = [[NSBundle mainBundle]
URLForResource:@"PhotoDiary"
withExtension:@"momd"];
self.managedObjectModel =
[[NSManagedObjectModel alloc]
initWithContentsOfURL:theURL];
}
return _managedObjectModel;
}
Listing 5.15 Laden des Datenmodells
Analog zu den Storyboards übersetzt Xcode während des Erzeugungsprozesses die Modelldateien in ein anderes Format, das für die Ausführung günstiger ist. Aus diesem Grund hat die Ressource in der App die Dateiendung .momd und nicht mehr .xcdatamodeld.
Das Application-Delegate erzeugt außerdem ein Objekt der Klasse NSPersistentStoreCoordinator über einen Lazy-Getter. Der Store-Koordinator verbindet die Entitätstypen eines Datenmodells mit einem Ablageort, den Unterklassen von NSPersistentStore beschreiben. In iOS gibt es drei mögliche Arten von Stores: SQLite-Datenbanken, Binärdateien und die nichtpersistente Ablage im Hauptspeicher. Das Fototagebuch verwendet eine SQLite-Datenbank. Neben dem Typ müssen Sie außerdem eine URL auf eine Datei im lokalen Dateisystem angeben. Die App legt diese Datei Diary.sqlite mit Hilfe der Methode applicationDocumentsDirectory aus der Kategorie NSFileManager(StandardDirectories) im Dokumentenordner ab; Sie können diese Methode natürlich auch durch die Methode applicationSupportDirectory ersetzen, um die Datei im Application-Support-Verzeichnis abzulegen.
- (NSURL *)persistentStoreURL {
NSString *theDirectory = [[NSFileManager defaultManager]
applicationDocumentsDirectory];
NSString *theFile = [theDirectory
stringByAppendingPathComponent:@"Diary.sql"];
return [NSURL fileURLWithPath:theFile];
}
Listing 5.16 URL für die SQLite-Datei erzeugen
Ein Store-Koordinator kann die Entitäten eines Datenmodells in unterschiedlichen Stores ablegen. Das können Sie über Konfigurationen festlegen. Das Fototagebuch macht davon allerdings keinen Gebrauch und verwendet nur einen SQLite-Store. Es legt also die Entitäten in Tabellen einer SQL-Datenbank ab. Sie können einen neuen Store über die Methode addPersistentStoreWithType:configuration:URL:options:error: des Store-Koordinators anlegen. Sofern Sie Ihre Entitäten alle im gleichen Store ablegen möchten, dürfen Sie den Wert nil für den Konfigurationsparameter verwenden.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if(_persistentStoreCoordinator == nil) {
NSURL *theURL = [[self applicationDocumentsURL]
URLByAppendingPathComponent:@"Diary.sqlite"];
NSError *theError = nil;
NSPersistentStoreCoordinator *theCoordinator =
[[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:
self.managedObjectModel];
if([theCoordinator addPersistentStoreWithType:
NSSQLiteStoreType configuration:nil URL:theURL
options:nil error:&theError]) {
self.persistentStoreCoordinator = theCoordinator;
}
else {
NSLog(@"Error: %@", theError);
}
}
return _persistentStoreCoordinator;
}
Listing 5.17 Erzeugung des Store-Koordinators
Mit dem Store-Koordinator können Sie einen Objektkontext erstellen, über den Sie Ihre Objekte verwalten.
5.3.5Der Objektkontext

Der Objektkontext ist eine Art Raum, in dem Ihre persistenten Objekte leben. Sie können in den Kontext Objekte aus der Datenhaltung hineinladen, neue Objekte erzeugen und enthaltene Objekte verändern oder löschen. Der Objektkontext erlaubt es außerdem, Änderungen schrittweise zurückzunehmen (Undo) und auch die Rücknahme wieder zurückzunehmen (Redo). Das alles hat allerdings zunächst keine Auswirkungen auf die Datenhaltung. Um die Änderungen Ihres Objektgraphen dauerhaft zu machen, müssen Sie den kompletten Objektkontext sichern.
Der Objektkontext ist also ein »Schmierpapier«, auf dem Sie Ihren Objektgraphen verändern können. Wenn Ihnen das Ergebnis gefällt, machen Sie eine Reinzeichnung von dem Graphen, indem Sie ihn dauerhaft in der Datenhaltung speichern. Sie erzeugen einen Objektkontext, indem Sie ein Objekt der Klasse NSManagedObjectContext erzeugen und diesem über die Property persistentStoreCoordinator einen Store-Koordinator zuweisen.
Eine neue Entität – so heißen die Objekte eines Entitätstyps – erzeugen Sie hingegen nicht über eine alloc-init-Kette, sondern lassen sie über die Klassenmethode insertNewObjectForEntityForName:inManagedObjectContext: der Klasse NSEntityDescription anlegen. Der erste Parameter enthält dabei den Namen des gewünschten Entitätstyps und der zweite den Objektkontext, der das neue Objekt aufnimmt. Sie brauchen danach das Objekt also nicht mehr selbst in einen Kontext einzufügen. Jedes Objekt kann allerdings nur zu einem Kontext gehören.
Über die Methode deleteObject: können Sie ein Objekt aus einem Objektkontext löschen. Für die Aktualisierung von Objekten gibt es keine gesonderten Methoden im Objektkontext. Sie weisen den Propertys des Objekts einfach nur neue Werte zu. Der Objektkontext bekommt diese Änderungen automatisch mit.
Die Änderungen des Objektgraphen im Objektkontext können Sie dauerhaft durch einen Aufruf der Methode save: sichern. Das Fototagebuch schreibt also durch diesen Aufruf alle Änderungen in die SQLite-Datenbank. Der Parameter ist ein Zeiger auf eine Objektreferenz der Klasse NSError. Über diesen gibt die Methode eventuell aufgetretene Fehler beim Speichern zurück. Typische Fehler sind hierbei die Verletzung der Integritätsbedingungen des Entitätstyps. Wenn Sie beispielsweise versuchen, einen Tagebucheintrag ohne Erzeugungszeit zu speichern, liefert der Aufruf einen Fehler, da dieses Attribut obligatorisch ist.
Falls die genaue Fehlerursache Sie nicht interessiert, dürfen Sie auch NULL für diesen Parameter angeben. Sie können auch aus dem Rückgabewert des Methodenaufrufs den Erfolg ermitteln. Ein typischer Methodenaufruf mit Rückgabe des Fehlerobjekts sieht also so aus:
NSError *theError = nil;
if(![theContext save:&theError]) {
// Verarbeitung des Fehlers
}
Listing 5.18 Sichern eines Objektkontextes mit Fehlerermittlung
Zeiger auf Objektreferenzen
In Cocoa Touch gibt es viele Methoden, die wie die Methode save: einen Zeiger auf eine Objektreferenz übergeben bekommen. Diese Zeiger sind C-Pointer und keine Objektreferenzen, weswegen Sie dafür NULL und nicht nil verwenden sollten. Umgekehrt sollten Sie Objektreferenzen auch nicht auf NULL setzen. Diese Unterscheidung ist eine Konvention, und ihre Missachtung führt (derzeit) zu keinen Compiler- oder Laufzeitfehlern. Es zeugt jedoch von gutem Stil, wenn Sie sich an die Konvention halten.
Über die Methode reset können Sie alle Änderungen in einem Objektkontext rückgängig machen. Dabei werden alle Objekte aus dem Kontext entfernt. Der Kontext befindet sich also nach dem Aufruf in seinem ursprünglichen Zustand.
5.3.6Die Nachrichten des Objektkontexts

Sie können sich über Änderungen Ihrer Objekte im Objektkontext informieren lassen. Da jede Änderung an den Modellobjekten mehrere Controller betreffen kann, sind Delegates für die Benachrichtigung indes ungeeignet. Stattdessen versendet der Objektkontext Benachrichtigungen, so dass er auch mehrere Controller auf einmal benachrichtigen kann. Es gibt drei Arten von Benachrichtigungen. Dabei ist das Objekt der Benachrichtigung immer der Objektkontext, in dem die App die Änderung durchgeführt hat, und das User-Info-Dictionary enthält die geänderten Objekte.
Der Objektkontext sendet eine Benachrichtigung vom Typ NSManagedObjectContextObjectsDidChangeNotification, wenn Sie die Attribute einer Entität im Kontext verändern. Unter dem Schlüssel NSUpdatedObjectsKey finden Sie die geänderten Objekte in der User-Info. Vor und nach dem Sichern versendet der Objektkontext jeweils eine Benachrichtigung vom Typ NSManagedObjectContextWillSaveNotification beziehungsweise NSManagedObjectContextDidSaveNotification. Die DidSave-Notification enthält im User-Info-Dictionary die geänderten Objekte unter den Schlüsseln NSUpdatedObjectsKey, NSInsertedObjectsKey und NSDeletedObjectsKey. Diese Benachrichtigungen erleichtern es Ihnen, die Anzeigen in Ihren Viewcontrollern aktuell zu halten. Sie können beispielsweise die Zellen in einem Tableview relativ einfach über diese Benachrichtigungen aktualisieren lassen. Wie das geht, beschreibt Abschnitt 5.5, »Tableviews und Core Data«.
5.3.7Anlegen und Ändern von Entitäten in der Praxis
In dem Fototagebuch gibt es einen eigenen Viewcontroller für die Erzeugung und Bearbeitung eines Tagebucheintrags. Er hat die Klasse DiaryEntryViewController, die ein Modellobjekt der Klasse DiaryEntry in der Property diaryEntry hält. Ein Eintrag kann dabei jeweils einen Text, ein Bild und einen Ton enthalten. Außerdem enthält der Eintrag das Erzeugungs- und das Aktualisierungsdatum. Diese beiden Werte setzt der Eintrag automatisch vor dem Speichern.
Bei der Anzeige des Controllers gibt es zwei Fälle: Entweder möchte der Nutzer einen bestehenden Eintrag verändern oder einen neuen anlegen. Vor der Anzeige setzt der Aufrufer dazu die Property diaryEntry entsprechend; entweder auf den zu verändernden Eintrag, oder er legt einen neuen an.
In Abbildung 5.10 sehen Sie die Anzeige des Diary-Entry-Viewcontrollers; er zeigt das Bild und den Text an und stellt über eine Werkzeugleiste Buttons zum Ändern des Bildes (links) und zum Abspielen und Ändern der Tonaufnahme (rechts) dar.
Das Speichern des Eintrags erfolgt über die Methode save: des Objektkontexts. Sie können auf den Objektkontext über die Methode managedObjectContext der Klasse NSManagedObject zugreifen. Diese Klasse ist ja die Oberklasse von DiaryEntry.
Abbildung 5.10 Anlegen eines neuen Eintrags
- (NSManagedObjectContext *)managedObjectContext {
return self.diaryEntry.managedObjectContext;
}
Listing 5.19 Objektkontext über den Diary-Entry-Viewcontroller ermitteln
Das Sichern geschieht im Controller in der Methode saveDiaryEntry, die der Controller immer aufruft, wenn der Nutzer etwas am Eintrag verändert hat.
- (BOOL)saveDiaryEntry {
BOOL theResult = NO;
NSError *theError = nil;
theResult = [self.managedObjectContext save:&theError];
if(!theResult) {
NSLog(@"saveItem: %@", theError);
}
return theResult;
}
Listing 5.20 Speichern des Kontexts
Der Controller kann jetzt den Eintrag verändern, indem er die Attribute des Eintrags setzt. Der Eintrag hat zwei Zeitstempel für den Erzeugungs- und den letzten Aktualisierungszeitpunkt des Eintrags. Diese Werte können Sie automatisch setzen, indem Sie in der Klasse DiaryEntry zwei Methoden der Klasse NSManagedObject überschreiben. Wenn Sie ein neues Objekt in einen Objektkontext einfügen, ruft Core Data die Methode awakeFromInsert des Objekts auf. Sie können diese Methode dazu verwenden, die Erzeugungs- und Aktualisierungszeit zu setzen.
- (void)awakeFromInsert {
[super awakeFromInsert];
NSDate *theDate = [NSDate date];
[self setPrimitiveValue:theDate forKey:@"creationTime"];
[self setPrimitiveValue:theDate forKey:@"updateTime"];
}
Listing 5.21 Vorbelegung der Erzeugungs- und Aktualisierungszeit
Die Aktualisierungszeit muss außerdem beim Sichern des Objekts aufgefrischt werden. Das können Sie über die Methode willSave bewerkstelligen. Der Name dieser Methode ist allerdings etwas missverständlich. Core Data ruft sie auf, bevor es die Änderungen des Objekts in der Datenhaltung sichert. Dazu zählt auch das Löschen der Entität, und Sie sollten diesen Sonderfall über die Methode isDeleted abfragen.
- (void)willSave {
[super willSave];
if(!self.isDeleted) {
NSDate *theDate = [NSDate date];
[self setPrimitiveValue:theDate forKey:@"updateTime"];
}
}
Listing 5.22 Erneuerung der Aktualisierungszeit beim Sichern
Die Methoden in Listing 5.21 und Listing 5.22 benutzen beide die Methode setPrimitiveValue:forKey: anstatt der Setter. Der Setter hat hier Nebeneffekte, die im Falle der Methode willSave zu einer Endlosrekursion führen. Durch die Verwendung von setPrimitiveValue:forKey: vermeiden Sie diese Nebeneffekte, da diese Methode nur den neuen Wert setzt.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.