06.03.2011

Übersicht

In dem Beispielprojekt wird ein einfaches Datenmodell mit Core Data angelegt, das Personen mit Vor- und Nachnamen speichern kann. Danach wird eine Funktionalität ergänzt, die das Importieren von Personen aus einer Textdatei in das Datenmodell erlaubt. Dazu wird eine Textdatei mit Vor- und Nachnamen zeilenweise eingelesen und für die darin enthaltenen Personen werden ManagedObjects angelegt und in Core Data gespeichert.

Außerdem gibt es ein paar Erklärungen zu den Methoden die Xcode beim Erstellen einer Core Data Anwendung anlegt. Beispielsweise wird erläutert, wie man zwischen SQLite, Binary, in Memory und XML als Speicherformat wechselt. Oder wo die Datei mit den abgespeicherten Daten zu finden ist.

Projekt anlegen

Als erstes wird ein Projekt angelegt indem im Hauptmenü File->New Project angeklickt wird. Im New Project-Dialog wird zuerst Command Line Tool und dann als Type Core Data ausgewählt. Abschließend bestätigt man mit Choose. Im nächsten Dialog wird als Name SimpleCommandLineImporter angegeben

Danach sollte in Xcode ein neues Projekt angelegt worden sein, das ungefähr so aussieht:

Unter Source befindet sich die Hauptklasse der Anwendung und unter Model wurde schon ein leeres Core Data Modell angelegt.

ManagedObjectModel

Wenn man die Datei SimpleCommandLineImporter.m öffnet, findet man mehrere Methoden. Einmal die Methode NSManagedObjectModel *managedObjectModel() in der das ManageObjectModel geladen wird.

NSManagedObjectModel *managedObjectModel() {
    
    static NSManagedObjectModel *model = nil;
    
    if (model != nil) {
        return model;
    }
    
	NSString *path = 
	      [[[NSProcessInfo processInfo] arguments] objectAtIndex:0];
	path = 
	      [path stringByDeletingPathExtension];
	NSURL *modelURL = 
	      [NSURL fileURLWithPath:[path stringByAppendingPathExtension:@"mom"]];
    model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    
    return model;
}

Diese mom-Datei wird beim Builden der Datei SimpleCommandLineImported.xcdatamodel erstellt und liegt unter normalen Umständen im Debug-Verzeichnis. (Abhängig von der gewählten Active Configuration). Der Dateiname ist identisch mit dem Projekt-Namen. In dem Beispiel also SimpleCommandLineImporter.mom.

Wenn die mom-Datei einen anderen Namen hat oder sich an einer anderen Stelle befindet, dann kann die Methode beispielsweise so angepaßt werden:

NSManagedObjectModel *managedObjectModel() {
    
    static NSManagedObjectModel *model = nil;
    
    if (model != nil) {
        return model;
    }
    
    
    NSString *filename = 
         [@"~/Documents/Xcode/SimpleCommandLineImporter/build/Debug/SimpleCommandLineImporter.mom" stringByResolvingSymlinksInPath];
    
    model = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:filename]];
    
    return model;
}

ManagedObjectContext

Die andere generierte Methode NSManagedObjectContext *managedObjectContext() ist für das Laden der Daten zuständig.

NSManagedObjectContext *managedObjectContext() {
	
    static NSManagedObjectContext *context = nil;
    if (context != nil) {
        return context;
    }
    
    context = [[NSManagedObjectContext alloc] init];
    
    NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: managedObjectModel()];
    [context setPersistentStoreCoordinator: coordinator];
    
    NSString *STORE_TYPE = NSSQLiteStoreType;
	
	NSString *path = [[[NSProcessInfo processInfo] arguments] objectAtIndex:0];
	path = [path stringByDeletingPathExtension];
	NSURL *url = [NSURL fileURLWithPath:[path stringByAppendingPathExtension:@"sqlite"]];
    
	NSError *error;
    NSPersistentStore *newStore = [coordinator addPersistentStoreWithType:STORE_TYPE configuration:nil URL:url options:nil error:&error];
    
    if (newStore == nil) {
        NSLog(@"Store Configuration Failure\n%@",
              ([error localizedDescription] != nil) ?
              [error localizedDescription] : @"Unknown Error");
    }
    
    return context;
}

In ihr kann auch das Format in dem die Daten gespeichert werden sollen verändert werden. Folgende Formate stehen auf dem Mac zur Verfügung:

NSString * const NSSQLiteStoreType;
NSString * const NSXMLStoreType;
NSString * const NSBinaryStoreType;
NSString * const NSInMemoryStoreType;

Standardmäßig ist der erste Wert gesetzt, der angibt, daß SQLite verwendet wird. Durch den Austausch der Konstante läßt sich das Format ändern. Benutzt man beispielsweise den zweiten Wert, dann werden die Daten als XML gespeichert. Dies ist besonders für Debug-Zwecke geeignet oder wenn die XML-Datei mit einem anderen Tool weiterverarbeitet werden soll.

Wenn man die Konstante für das Format anpaßt, dann sollte auch die Dateiendung geändert werden. Falls XML gewählt wurde, sollte auch als Dateiendung xml verwendet werden:

NSURL *url = 
    [NSURL fileURLWithPath:[path stringByAppendingPathExtension:@"xml"]];

Diese Datei liegt übrigens in dem gleichen Verzeichnis, wie die mom-Datei.

Core Data Model erstellen

Wenn die Datei SimpleCommandLineImporter.xcdatamodel selektiert wird, dann wechselt die Ansicht in den Core Data Design Modus.

Als erstes soll jetzt eine neue Entity Person mit den Attributen Vorname und Name in dem Modell ergänzt werden.

Es gibt mehrere Möglichkeiten eine neue Entity anzulegen. Einmal über das Hauptmenü Design->Data Model->Add Enity oder über das kleine Plus-Zeichen, welches der Entity-Ansicht zugeordnet ist.

Nach dem Anlegen einer neuen Entity läßt sich ein Name wählen. In dem Beispiel wird Person verwendet. Wenn die Entity Person selektiert ist, lassen sich über das Plus-Zeichen der Attribut-Ansicht zusätzliche Attribute hinzufügen. Dies kann aber auch über das Hauptmenü gemacht werden, indem Design->Data Model->Add Attribute ausgewählt wird. In dem Beispiel werden zwei Attribute hinzugefügt. Beide sind vom Type String und nicht Optional, Transient und Indexed.

Einlesen von Textdateien

Die Personen, die in Core Data gespeichert werden sollen, liegen in einer Textdatei (Personen.txt) vor. In dem Beispiel wird davon ausgegangen, daß sich die Datei im Verzeichnis Dokumente des aktuell angemeldeten Benutzers befindet. Sie hat folgendes Format:

Jörn Hameister
Donald Duck
Bart Simpson

Eine genauere Erkärung, wie man so eine Datei einliest findet man auf der Seite Einlesen von Dateien mit Objective-C

In der Datei SimpleCommandLineImporter.m wird also eine Methode zum Einlesen dieser Textdatei ergänzt. Sie sieht folgendermaßen aus:

NSArray* readPersons() { 
    //Zeilenweise Textdatei einlesen:
    NSString *filename = 
    [@"~/Documents/Personen.txt" stringByResolvingSymlinksInPath];
    NSError *error = nil;

    NSString *filecontent = 
    [[NSString alloc] initWithContentsOfFile:filename
                                    encoding:NSUTF8StringEncoding
                                       error:&error];

    if(error!=nil) {
        NSLog(@"Cannot read file: %@",filename);
        return nil;
    }
    
    //Parse filecontent
    NSArray *lines = [filecontent componentsSeparatedByString:@"\n"];
    for(id names in lines) {
        NSLog(@"%@", names);
    } 
    
    return lines;
}

Die Personen werden zeilenweise eingelesen und in einem NSArray als NSString gespeichert.

Importieren von Personen

Zum Importieren wird in der main-Methode der Datei SimpleCommandLineImporter.m folgender Quellcode an der Stelle // Custom code here... ergänzt:

// Read persons 
NSArray* persons = readPersons();

for(id person in persons) {
    NSArray *firstnameLastname =  [person componentsSeparatedByString:@" "];
    
    // Add a new Person
    NSManagedObject *personMO = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
    [personMO setValue:[firstnameLastname objectAtIndex:0] forKey:@"personfirstname"];
    [personMO setValue:[firstnameLastname objectAtIndex:1] forKey:@"personname"];        
}

Zuerst wird die Methode zum Einlesen der Personen aufgerufen. Danach wird jeder Personen-String (z.B. Jörn Hameister) am Leerzeichen aufgesplittet und in einem NSArray abgelegt. Dadurch wurde der Vorname vom Nachnamen getrennt.

Für die Personen, welche in Core Data gespeichert werden sollen, wird ein NSManagedObject erzeugt. Als Entity-Name wird Person angegeben. Anschließend werden die Attribute mit Werten gefüllt. Zuerst der Vorname, dann der Nachname. Es wird jeweils als key der Attributname verwendet.

In dem Beispiel wird der Einfachheit halber davon ausgegangen, daß eine Person immer einen Vor- und Nachmanen hat.

Nach dem Kompilieren und Starten werden die Daten aus der Datei eingelesen und in Core Data abgelegt. Wie die Daten ausgelesen werden, wird in dem Artikel: Auslesen mit NSFetchRequest erklärt.