Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Geleitwort
Vorwort
1 Hello iPhone
2 Die Reise nach iOS
3 Sehen und anfassen
4 Alles unter Kontrolle
5 Daten, Tabellen und Controller
6 Models, Layer, Animationen
7 Programmieren, aber sicher
8 Datenserialisierung und Internetzugriff
9 Multimedia
10 Jahrmarkt der Nützlichkeiten
Stichwort

Buch bestellen
Ihre Meinung?

Spacer
Apps programmieren für iPhone und iPad von Klaus M. Rodewig, Clemens Wagner
Das umfassende Handbuch
Buch: Apps programmieren für iPhone und iPad

Apps programmieren für iPhone und iPad
Rheinwerk Computing
1172 S., geb., mit DVD
49,90 Euro, ISBN 978-3-8362-2734-6
Pfeil 5 Daten, Tabellen und Controller
Pfeil 5.1 Benachrichtigungen
Pfeil 5.1.1 Benachrichtigungen empfangen
Pfeil 5.1.2 Eigene Benachrichtigungen verschicken
Pfeil 5.2 Layoutanpassungen und Viewrotationen
Pfeil 5.2.1 Lang lebe das Rotationsprinzip!
Pfeil 5.2.2 Anpassung des Layouts
Pfeil 5.3 Core Data
Pfeil 5.3.1 Datenmodellierung
Pfeil 5.3.2 Implementierung von Entitätstypen
Pfeil 5.3.3 Dokumentenordner und Application-Support-Verzeichnis
Pfeil 5.3.4 Einbindung von Core Data
Pfeil 5.3.5 Der Objektkontext
Pfeil 5.3.6 Die Nachrichten des Objektkontexts
Pfeil 5.3.7 Anlegen und Ändern von Entitäten in der Praxis
Pfeil 5.4 Texte, Bilder und Töne verwalten
Pfeil 5.4.1 Viewanpassungen für die Systemleisten
Pfeil 5.4.2 Die Tastatur betritt die Bühne
Pfeil 5.4.3 Fotos aufnehmen
Pfeil 5.4.4 Töne aufnehmen und abspielen
Pfeil 5.5 Tableviews und Core Data
Pfeil 5.5.1 Tableviews
Pfeil 5.5.2 Tabellenzellen gestalten
Pfeil 5.5.3 Zellprototypen über das Storyboard definieren
Pfeil 5.5.4 Zellprototypen per Programmcode bereitstellen
Pfeil 5.5.5 Der Target-Action-Mechanismus und Tabellenzellen
Pfeil 5.5.6 Zellen löschen
Pfeil 5.6 Core Data II: Die Rückkehr der Objekte
Pfeil 5.6.1 Prädikate
Pfeil 5.6.2 Aktualisierung des Tableviews
Pfeil 5.6.3 Das Delegate des Fetched-Results-Controllers
Pfeil 5.6.4 Tabelleneinträge suchen
Pfeil 5.7 Inhalte teilen
Pfeil 5.7.1 Integration in das Fototagebuch
Pfeil 5.7.2 Eigene Aktivitäten bereitstellen
Pfeil 5.8 Collectionviews
Pfeil 5.8.1 Der Collectionviewcontroller
Pfeil 5.8.2 Gitterdarstellung
Pfeil 5.8.3 Zellen und die Datenquelle
Pfeil 5.8.4 Ergänzende Views
Pfeil 5.8.5 Freie Layouts
Pfeil 5.8.6 Freie Layouts und ergänzende Views
Pfeil 5.8.7 Decorationviews

Rheinwerk Computing - Zum Seitenanfang

5.3Core DataZur nächsten Überschrift

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.


Rheinwerk Computing - Zum Seitenanfang

5.3.1DatenmodellierungZur nächsten ÜberschriftZur vorigen Überschrift

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

Abbildung 5.4 Das Datenmodell in der Dialogdarstellung

In der linken Spalte 1 des Modelleditors finden Sie die Hauptelemente des Datenmodells. Dazu gehören Entitätstypen, Suchanfragen und Konfigurationen, die Sie über den linken Plus-Button 6 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 2 und die Relationships 3 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 4 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 7 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 5 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 8 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

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 (alt+cmd+3) 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

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 alt+cmd+3 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:

  1. Ein transientes Attribut wird nicht persistent gespeichert. Es geht also spätestens nach dem Programmende verloren.
  2. 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.
  3. 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

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

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.


Rheinwerk Computing - Zum Seitenanfang

5.3.2Implementierung von EntitätstypenZur nächsten ÜberschriftZur vorigen Überschrift

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.


Rheinwerk Computing - Zum Seitenanfang

5.3.3Dokumentenordner und Application-Support-VerzeichnisZur nächsten ÜberschriftZur vorigen Überschrift

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

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.


Rheinwerk Computing - Zum Seitenanfang

5.3.4Einbindung von Core DataZur nächsten ÜberschriftZur vorigen Überschrift

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.


Rheinwerk Computing - Zum Seitenanfang

5.3.5Der ObjektkontextZur nächsten ÜberschriftZur vorigen Überschrift

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.


Rheinwerk Computing - Zum Seitenanfang

5.3.6Die Nachrichten des ObjektkontextsZur nächsten ÜberschriftZur vorigen Überschrift

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«.


Rheinwerk Computing - Zum Seitenanfang

5.3.7Anlegen und Ändern von Entitäten in der PraxisZur vorigen Überschrift

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

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.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück




Copyright © Rheinwerk Verlag GmbH, Bonn 2014
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de


  Zum Katalog
Zum Katalog: Apps programmieren für iPhone und iPad






Neuauflage: Apps programmieren für iPhone und iPad
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Einstieg in Objective-C 2.0 und Cocoa






Einstieg in Objective-C 2.0 und Cocoa


Zum Katalog: Spieleprogrammierung mit Android Studio






Spieleprogrammierung mit Android Studio


Zum Katalog: Android 5






Android 5


Zum Katalog: iPhone und iPad-Apps entwickeln






iPhone und iPad-Apps entwickeln


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo