Hilfreiche Informationen und Tipps für iOS-Entwickler

iOS Essentials für Entwickler
Kommentare

Das iOS SDK bietet eine sehr gute Basis an Standard-Controls und Frameworks. Allerdings benötigt man zur Erstellung von anspruchsvollen Apps doch das eine oder andere Control, bzw. Framework, das nicht im SDK-Umfang enthalten ist. Es ist also essenziell wichtig, bestehende Frameworks und Werkzeuge zu kennen und zu beherrschen.

In diesem Artikel wird der Einsatz von verschiedenen Bibliotheken, Werkzeugen und Herangehensweisen vorgestellt, die ein iOS-Entwickler kennen sollte. Wir wollen nicht „das Rad neu erfinden“, im Fokus steht echte Arbeitserleichterungen und -beschleunigungen für die Schaffung von anspruchsvollen iOS-Apps. Da die Auswahl an Bibliotheken und externen Komponenten mittlerweile sehr groß ist und die Möglichkeiten, eine App zu entwickeln, nahezu unbeschränkt sind, haben wir für diesen Artikel eine Auswahl getroffen. Dabei führen wir die Inhalte auf, die aus unserer Erfahrung die größte Praxisrelevanz besitzen und die wir uns zu Beginn der iOS-Entwicklung u. a. selbst gewünscht hätten. Leider lassen sich in einem Artikel alleine die Frameworks und Einsatzmöglichkeiten nicht erschöpfend und vollständig erklären – allerdings sollte deren Kenntnis und Einordnung den tieferen Einstieg deutlich erleichtern. Der Artikel wendet sich an iOS-Entwickler, die bereits grundlegende Erfahrung in der Entwicklung mit Objective-C und Xcode haben. Daher handelt es sich bei den ausgeführten Beispielen und Tipps auch nicht um vollständige Einstiegs-Tutorials.

Wir wollen zunächst den Kontext der mobilen Softwareentwicklung näher erläutern. Dabei ist es wichtig, ein solides Verständnis der Grundbausteine einer iOS-App-Architektur zu haben bzw. zu bekommen. Die folgenden Themen sehen wir als eine Auswahl von typischen Anforderungen und Maßgaben an die native Softwareentwicklung von mobilen Anwendungen. Diese Punkte stellen die Grundlage und Motivation für die nachfolgend beschriebenen Frameworks und Komponenten dar:

  • MVC: Das Model-View-Controller-Konzept ist der Basisbaustein jeder iOS-Anwendung bzw. App-Architektur. Die drei Architekturbausteine Model, View und Controller spielen dabei die Hauptrolle. Im iOS-Framework sind verschiedene Interaktionsmöglichkeiten zwischen diesen Komponenten vorgesehen, die im nächsten Abschnitt genauer erklärt werden.
  • Async Processing: Um eine gute User Experience und die optimale Ausnutzung der mobilen Vorteile zu gewährleisten, muss eine App schnell und flüssig reagieren. Dazu ist es sehr hilfreich, die aktuellen Mechanismen für asynchrone Datenverarbeitung und Threading zu kennen und zu nutzen. Um sie auch umzusetzen, liefert das iOS SDK mit dem seit iOS 4 hinzugekommenen Sprachkonstrukt „Blocks“ und der Grand-Central-Dispatch-Bibliothek bereits geeignete Mittel mit.
  • Networking und Integration: Meist kommen Daten und Informationen, die in einer App dargestellt und verarbeitet werden, von einem externen System. Zu einem hohen Maße hat sich dabei die Integration mittels HTTP durchgesetzt. Zwar bietet iOS von Hause aus mit der NSURLConnection ein gutes API dafür an, das synchron und asynchron nutzbar ist, jedoch kann der Einsatz des Open-Source-ASIHTTPRequest-Frameworks noch ein paar weitere Vorteile und Erleichterungen mit sich bringen, wie später beschrieben wird. Ein typisches Problem mobiler Anwendungen ist dabei die Abhängigkeit zur Internetverbindung. Mit der iOS4-Beispielapplikation Reachability von Apple wurde gezeigt, wie das System-Configuration-Framework genutzt werden kann, um den Netzwerkstatus für iOS-Geräte zu ermitteln und dann adäquat auf diesen Status oder Statusänderungen in der Anwendung zu reagieren. Die Reachability-Klassen können natürlich auch für eigene Anwendungen gute Dienste leisten.
  • Offlinefähigkeit/lokale Persistenz: Sehr häufig stellt sich die Frage nach der Möglichkeit, eine App auch ohne Internetverbindung nutzen zu können. Selbst für Anwendungen, die man typischerweise online nutzt (z. B. Twitter) hat sich die Best Practice etabliert, die Anzeige und Verarbeitung der Informationen (z. B. in TableViews) von der Integration/Abfrage dieser Daten aus entfernten Systemen zu entkoppeln. Nicht zuletzt auch, um ein deterministisches Verhalten und eine stabile User Experience bei der Nutzung der Anwendung zu ermöglichen. Dabei werden die Daten in einer lokalen Datenbank oder im Filesystem gespeichert. Mit dem Core-Data-Framework, der FetchRequest– und der FetchResultsController-Klasse steht für die gängigen Anwendungsfälle ein hervorragendes Werkzeug aus dem iOS SDK direkt zur Verfügung.
  • Rich UI: Die beste Anwendung lebt von ansprechenden UIs bzw. UI Controls. Das iOS SDK ist leider sehr beschränkt, was die Auswahl an UI-Komponenten betrifft. Und wenn man nicht will, dass die eigene App aussieht wie jede andere oder wie eine System-App, sollte man nicht auf  mächtige externe (Open-Source-)Frameworks und Bibliotheken wie Three20, EGO*, oder OpenFlow verzichten. Ein paar Beispiele für die Verwendung dieser Ergänzungen folgen im weiteren Verlauf des Artikels.

Selbstverständlich sind nicht für jede App alle hier aufgeführten Punkte relevant, jedoch spiegeln diese Anforderungen den Kontext von zahlreichen Anwendungen wider.

Aufmacherbild: Close up interior old car von Shutterstock / Urheberrecht: jaret kantepar

[ header = Essenzielle Plattformen & MVC-Interaktionsmuster ]

Das iOS SDK bringt schon ein reichhaltiges Angebot an nützlichen Klassen und Bibliotheken mit, die man direkt nutzen kann und auch sollte. Folgend werden für den zuvor benannten Kontext ein paar der wichtigsten Mechanismen und Möglichkeiten detaillierter vorgestellt.

Fundamentaler Eckpfeiler für die iOS-Entwicklung ist das Model-View-Controller-Muster. Die Prinzipien dieses Musters durchdringen fast jedes verfügbare Framework bei iOS. Die bevorzugte Variante von MVC im iOS-Kontext entspricht eigentlich eher dem verwandten MVP- (Model-View-Presenter-)Muster, bei dem zwischen Model und View ausschließlich über den Controller kommuniziert wird. Der Controller bestimmt die anzuzeigende View und füllt deren Inhalte mit Daten aus dem Model bzw. sorgt dafür, dass Dateneingaben in der View – falls erforderlich – wieder im Model landen. Man sollte versuchen, alles, was Businesslogik betrifft, aus dem Model und der View herauszuhalten und diese quasi möglichst „dumm“ zu belassen. Damit werden sowohl Model als auch View einfacher wiederverwendbar, die Programmlogik ist zentralisiert und somit die gesamte App einfacher zu warten und zu erweitern. Die Interaktionsmöglichkeiten und Mechanismen sind in Abbildung 1 dargestellt.

Abb. 1: Model View Controller – Interaktionsmuster

  • Model <––> View: Keine direkte Kommunikation oder Abhängigkeit!
  • Controller ––> Model: Im Normalfall hat der Controller Zugriff auf das Model und kann Informationen abfragen oder verändern bzw. hinzufügen. Der Zugriff erfolgt üblicherweise über eine Instanzvariable des Controllers, in der das Model gehalten wird, oder über ein Datenzugriffsframework wie Core Data (z. B. ManagedObjectContext, FetchResultsControllerFetchRequest), das den Zugriff und die Manipulation des Models nochmals kapselt.
  • Model ––> Controller: iOS stellt über die NSNotificationKlasse eine Möglichkeit bereit, um Komponenten lose zu koppeln und das Observer-Muster zu realisieren. Dabei sendet eine Klasse (z. B. das Model) eine NSNotification über das NSNotificationCenter Object wie eine Radiostation aus, interessierte Klassen (z. B. Controller) können sich als Beobachter (Observer) für diese NSNotification am NSNotificationCenter registrieren und werden dann (asynchron) über die Notification in Kenntnis gesetzt. So kann der Controller beispielsweise eine View aktualisieren, wenn ein Hintergrundprozess neue Daten geliefert und sich somit das Model verändert hat.
  • Controller ––> View: Views werden als Instanzvariablen eines Controllers gehalten. Sind Views mittels XIB (serialisierter UI-Komponentenbaum) definiert, also über das Zusammenklicken mit dem Interface Builder, können View-Elemente über so genannte Outlets an Instanzvariablen eines Controllers gebunden werden (markiert durch IBOutlet). Der Controller hat somit direkten Zugriff auf diese View-Elemente und kann die View entsprechend manipulieren und abfragen. Allerdings bekommt der Controller dabei nicht aktiv mit, wenn die View verändert wird, bzw. wenn der User mit dem UI interagiert. Dafür gibt es aber die im Folgenden beschriebenen Mechanismen für die Kommunikation von View zu Controller.
  • View ––> Controller: Die View kommuniziert nie fest gekoppelt mit einem Controller. Vielmehr wird für eine View ein Controller konfiguriert oder sie wird mit ihm parametrisiert. Für die Kommunikation von View zu Controller gibt es zwei Möglichkeiten:

1. Das einfachste Interaktionsmuster ist der Target-Action-Mechanismus. Dabei werden Aktionen, die im UI ausgelöst werden, ähnlich wie Outlets an Methoden des Controller gebunden (markiert durch IBAction). Der Controller registriert sich dabei als Target für eine Action des User Interface, wie zum Beispiel „Knopf gedrückt“ (TouchUpInside). Wird diese Aktion auf dem UI ausgeführt, wird im Controller die entsprechende Target-Action-Methode aufgerufen. Das „Binden“ zwischen Action und Target kann entweder im Interface Builder durch Zusammenklicken, oder auch programmatisch im Controller erfolgen.

2. Als weiteres Interaktionsmuster kommt das Delegate-Muster zum Einsatz. Dieser Mechanismus ist sehr stark verbreitet bei der iOS-Entwicklung. Üblicherweise verfügt eine UI View über ein delegate-Attribut. Das Objekt, das als Delegate für die View gesetzt wird, realisiert ein bestimmtes, View-spezifisches Protocol, das die zu implementierenden Callback-Methoden (Delegate-Methoden) definiert. Meist setzt sich der Controller selbst als Delegate für eine View und kann damit auf Manipulationen und Ereignisse des UI reagieren. Damit kann eine View bei einer Veränderung oder Aktion Code im Controller aufrufen. Beispielsweise erhält ein Controller so die Möglichkeit, Texte schon während der Eingabe in ein Textfeld zu überprüfen und ggf. Autovervollständigungsvorschläge oder Formatierungen (z. B. bei Telefonnummern) zu machen.

Ziel sollte es immer sein, das User Interface nicht zu blockieren und dafür zu sorgen, dass länger laufende Aktionen nicht im MainThread (UI Thread) ausgeführt werden. Üblicherweise sorgt der Controller dafür, dass eigene Worker Threads dafür gestartet werden, die dann wiederum mit dem UI Thread interagieren, um beispielsweise einen Fortschrittsbalken anzuzeigen, oder eine View zu aktualisieren, sobald der Hintergrundprozess beendet ist. Die Vertrautheit beim Umgang mit Threads und Callbacks, bzw. allgemein asynchronen APIs, ist sicher ein wesentliches „Essential“ für die iOS-Entwicklung. Nur so ist es möglich, eine flüssige User Experience und das typische „native App“-Verhalten zu erreichen. Ein geeignetes Hilfsmittel von iOS nennt sich Grand Central Dispatch (GCD). Um allerdings die Funktionsweise zu verstehen und das Framework richtig anzuwenden, ist es hilfreich, sich zunächst mit dem in iOS4 eingeführten Sprachkonstrukt der Blocks zu befassen.

[ header = Blocks ]

Bei Blocks handelt es sich um ein Sprachfeature (Syntax) von Objective-C, um Funktionen quasi on the Fly zu deklarieren. Ein Code-Block stellt zunächst eine Abfolge von Statements innerhalb von geschweiften Klammern {} dar. Blocks vereinfachen Funktions-Callbacks, indem sie meist als „inline“-Parameter an Methoden übergeben werden, die den Block dann irgendwann ausführen. Dabei gibt es intelligente Konzepte für die Nutzung von lokalen Variablen und referenzierten Objekten innerhalb der Blocks. Betrachten wir als Beispiel die Verwendung eines Blocks als Parameter in dem API der Klasse NSDictionary für die Methode dictionaryWithObjectsAndKeys, mit den drei Block-Parametern (key, value und stop), die durch ^(…) spezifiziert werden (Listing 1).

// Beispiel: Block in NSDictionary API
NSDictionary *dictionary = 
                    [NSDictionary dictionaryWithObjectsAndKeys:@"val1", @"k1", 
                                                               @"val2", @"k2", 
                                                               @"val3", @"k3", nil];
double localVar = 42;
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
  NSLog(@"Key: '%@' and Value: '%@'", key, value);
  if ([@"k2" isEqualToString:key]) {
    *stop = YES; // sematics for stopping
    NSLog(@"localVar: '%f'", localVar);
    // localVar = 666  THIS IS ILLEGAL use __block keyword
  }
}];

Dieser Code iteriert über jedes Key/Value-Paar im NSDictionary und gibt sie mittels NSLog aus. Die Iteration stoppt, wenn der Key „k2“ gefunden wurde, indem *stop = YES gesetzt wird. Dabei wird auch der Wert der vor dem bzw. außerhalb des Blocks deklarierten lokalen Variablen localVar ausgegeben. Hier ist jedoch Vorsicht geboten: Lokale Variablen werden beim Start der Block-Ausführung kopiert und sind „read-only“. Das heißt, in unserem Beispiel kann localVar nicht innerhalb des Blocks verändert werden. Will man jedoch den Wert von localVar auch außerhalb des Block-Kontexts verändern, muss die Deklaration der Variablen einfach __block double localVar = 42; lauten. Mit dem Schlüsselwort __block kann der Scope eines Blocks erweitert werden. Jedoch muss man sich auch hier bewusst sein, dass man evtl. nicht genau sagen kann, wann ein Block ausgeführt wird, und dementsprechend nicht weiß, wann die Ausführung des Blocks die localVar verändert hat.

Falls man auf außerhalb definierte Objekte innerhalb eines Blocks zugreifen will, sollte man wissen, dass diese automatisch retained werden, und sobald der Block releast wird, ebenfalls freigegeben werden. Greift man auf Instanzvariablen zu, wird automatisch self retained. Will man selbst Blocks definieren und in eigenen Methodensignaturen verwenden, legt man sie als typedef an (typedef <RückgabeTyp> (^<BlockTypName> (<BlockParameterListe>)) an. Blocks können selbst wie Objekte in Variablen gespeichert werden (Listing 2).

// Beispiel: Definition eines Blocks
typedef double (^mysqare_t)(double op);
mysqare_t square;
square = ^(double op){ return op * op; };

double squareOf5 = square(5);
NSLog(@"squareOf5: %f", squareOf5);

// Beispiel: Definition eines Blocks als Methodenparameter
// z.B. dynamisches hinzufügen einer Taschenrechnerfunktion
typedef double (^mysqare_t)(double op);
mysqare_t squareOperation;

- (void)calculatorSquareOperation:(NSString *)operationName 
               withOperationBlock:(mysqare_t) operationBlock {
            squareOperation = operationBlock;
} 

// … spätere Nutzung der gespeicherten Square-Funktion
- (double)calculateSquare:(double) operand {
return squareOperation(operand);
}

Weitere typische Verwendungen und gute Einsatzmöglichkeiten für Blocks findet man in iOS bei:

  • Enumerationen (Ausführen eines Blocks auf Collection-Einträgen)
  • Sortierung (Blocks als Komparatorfunktion)
  • Notifications (Block bei Notification ausführen)
  • Error Handler (Falls Fehler passiert, dann Block ausführen)
  • Completion Handler (Wenn Verarbeitung fertig, dann Block ausführen)
  • Multi-Threading (Block in eigenem Thread ausführen)!

Der Punkt Multi-Threading spielt eine zentrale Rolle, um asynchrones Verhalten und somit performante Apps zu entwickeln.

Bei Grand Central Dispatch handelt es sich um ein reines C-API, das es erlaubt, Operationen, die als Blocks spezifiziert sind, zu queuen und auszuführen. Das kann seriell, concurrent oder asynchron erfolgen. Blocks werden dabei in eigenen Threads ausgeführt (Thread Queue). Falls eine Operation blockiert, wird somit nur die damit assoziierte Queue blockiert – andere Queues werden weiterhin abgearbeitet (Abb. 2).

Abb. 2: Grand Central Dispatch Queues

Anwendung findet GCD typischerweise bei berechnungsintensiven, zeitaufwändigen und blockierenden Aktivitäten wie Zugriffe auf das Filesystem oder Netzwerk. Dabei werden diese Aktivitäten aus dem MainThread in einen anderen Thread verlagert, und das UI bleibt flüssig für die Interaktionen mit dem Nutzer. Nebenbei angemerkt, bringt GCD auch Vorteile auf Multi-Core-Systemen wie dem iPad2, da eine höhere „echte“ Parallelität erreicht werden kann. Die Queues von GCD funktionieren nach dem FIFO-Prinzip (first in, first out) und die Operationen zum Queuen und Dequeuen funktionieren atomar. Die Semantik für den create/retain/release-Lebenszyklus einer GCD Queue wird im nächsten Beispiel illustriert:

// Beispiel: Lebenszyklus einer GCD Queue
dispatch_queue_t q = dispatch_queue_create("com.arconsis.myqueue",
 NULL);
// Pair with retain & release. 
dispatch_retain(q);
// The last 'release' deallocates queue (when queue is empty) 
dispatch_release(q);

Das dispatch_release gibt den Speicher erst frei, wenn die Queue leer ist, also die letzte Operation/Block ausgeführt wurde. Es ist wichtig, einen guten, eindeutigen Namen für die Queues im dispatch_queue_create zu vergeben, da er in den Crash-Logs auftaucht und das Debuggen von nebenläufigen Multi-Threading-Anwendungen erleichtert. Blocks können mittels GCD synchron über dispatch_sync ausgeführt werden. Das ermöglicht einen hervorragenden Einsatz für Synchronisierungsthematiken bei Concurrent-Anwendungen, anstelle der Verwendung von Locks. Dabei ist die Implementierung von GCD Queues auf iOS effizienter umgesetzt als die Nutzung von Locks:

// Beispiel: Synchronization in kritischer Codesektion
-(BOOL)debitAccount:(Account *)account 
withTransaction:(Transaction *)transaction {
dispatch_sync(account.queue, ^{
account.balance -= transaction.amount;
});

return YES;
}

Man muss sich nicht um die Verwaltung von Locks kümmern oder Deadlocks fürchten, da die iOS Runtime die Serialisierung der Blocks übernimmt, wenn sie beendet werden. Allerdings sollte man beachten, dass Queues streng dem FIFO-Prinzip gehorchen, also ein Schachteln von dispatch_sync auf der gleichen Queue zu einem Deadlock führt, was extrem schwer zu debuggen ist.

Wie bereits angesprochen, liegt der größte Verwendungszweck jedoch im Auslagern von Aktivitäten aus dem MainThread, um diesen nicht zu blockieren, was durch dispatch_async erreicht wird. Dabei ist es wichtig, bei der Kommunikation zwischen verschiedenen Threads die richtige Queue auszuwählen, damit beispielsweise nach einer Hintergrundverarbeitung in einem Worker Thread, das UI durch den MainThread aktualisiert wird. Listing 3 zeigt ein gängiges Muster.

// Beispiel: Asynchrone Kommunikation zwischen Queues/Threads
-(IBAction) onClick:(UIButton *) sender {
dispatch_async(queue, ^{
UIImage *image = [self computationIntensiveTask];
// callback queue - but: UIKit calls should happen on main thread! 
dispatch_async(dispatch_get_main_queue(), ^{
[image drawInRect:CGRectMake(0, 0, 320, 100)];
});
});
}

In Listing 3 wird durch dispatch_get_main_queue die Queue des MainThread geholt und die Aktualisierung des UI (bzw. Image zeichnen) erfolgt dann in diesem Thread. Ein übliches Beispiel für dieses Muster ist das Einschalten eines Progress-Indikators vor der Hintergrundverarbeitung und das Abschalten des Indikators sowie die Aktualisierung des UI im MainThread nach Beendigung der Verarbeitung. Durch die bereits besprochenen „Scoping“-Fähigkeiten der Blocks kann somit auf Elemente aus dem umgebenden Kontext zugegriffen werden (z. B. Indikator in lokaler Variable gemerkt und nicht notwendigerweise global). Um die im Block-Kontext gültige Queue zu erhalten, kann die Funktion dispatch_get_current_queue verwendet werden. Beim Design von eigenen Methoden, die Queues als Parameter und geschachtelte Blocks verwenden, muss darauf geachtet werden, dass dispatch_queue_t retained und released wird. Listing 4 zeigt dafür ein Beispiel.

// Beispiel: Definition einer Async API und bsp. Retain und Release für Queues 
typedef double(^callback_block_t)(double param);



-(void)asyncOnQueue:(dispatch_queue_t) queue withBlock:(callback_block_t) block {
    dispatch_retain(queue);
    dispatch_async(background_queue, NULL), ^{
        dispatch_async(queue, ^{
            block(42);
        });
        dispatch_release(queue);
    };
}

[ header = Core Data und FetchResultsController ]

Für die lokale Persistenz von Daten beziehungsweise Objektgraphen bietet es sich an, das Core-Data-Framework zu verwenden, das iOS mitbringt. Kurz zusammengefasst handelt es sich um ein Werkzeug, um Datenstrukturen (Objektgraphen) mit einem grafischen Designer in XCode zu spezifizieren, aus denen man NSManagedObjects (also Klassen) generieren und dann objektorientiert manipulieren kann. Durch das Core-Data-Framework werden diese Objektstrukturen persistent auf dem Gerät in einer SQLite-3-Datenbank gespeichert, ohne dass man sich als Entwickler um SQL-Abfragen kümmern muss. Es gibt ein paar Analogien zu O/R Mappern wie JPA oder Hibernate aus dem Java-Bereich. Im Prinzip werden Objekte in einem so genannten ManagedObjectContext erzeugt/registriert, der das (Nach-)Laden und Speichern der Daten übernimmt.

Wir wollen im Weiteren ein paar Anregungen für die Verwendung dieses Framework geben, vor allem im Kontext von Apps, die die bereits erwähnten Muster bzgl. Asynchronität verwenden. Denn Achtung: Der ManagedObjectContext ist nicht threadsafe. Zunächst aber ein Tipp für das Generieren beziehungsweise Erweitern von Entity-Klassen (Custom NSManagedObjects) aus dem Core Data Model. Es kann oft sinnvoll sein, die von Core Data generierten Custom NSManagedObjects und weitere Funktionalität oder Hilfsmethoden zu ergänzen. Möchte man dann auch das Objektmodel mittels XCode Designer erweitern, würde man die manuellen Änderungen in den generierten Klassen überschreiben und somit verlieren. Hier können die Objective-C Categories gute Dienste leisten, um generierten von manuellem Code zu trennen, ohne auf Delegation oder Vererbung zurückgreifen zu müssen. Bei Categories handelt es sich quasi um weitere Codeteile einer Klasse, die in einem oder mehreren eigenen Files (.h und .m) ausgelagert werden.

Listing 5 zeigt ein kleines Beispiel für die Erweiterung einer von Core Data generierten Klasse Speaker um zwei Factory-Methoden (Erzeugung eines NSManagedObjects im NSManagedObjectContext), die in der Category (Speaker + Additions) implementiert werden.

// Beispiel: Categoryerweiterung (Speaker+Additions.h) der Klasse Speaker
@interface Speaker (SpeakerAdditions)

#pragma - factory method to create entity
+(Speaker *)createSpeakerInManagedObjectContext:(NSManagedObjectContext *)context;
+(Speaker *)speakerWithData:(NSDictionary*)speakerData 
     inManagedObjectContext:(NSManagedObjectContext *)context;

@end

// Beispiel: Implementierung Categoryerweiterung (Speaker+Additions.m) 
@implementation Speaker (SpeakerAdditions)
+(Speaker *)createSpeakerInManagedObjectContext:(NSManagedObjectContext *)context {
    return [NSEntityDescription insertNewObjectForEntityForName:@"Speaker"
                                         inManagedObjectContext:context];
}
+(Speaker *)speakerWithData:(NSDictionary*)speakerData inManagedObjectContext:(NSManagedObjectContext *)context {
 // Implementierung um Entity aus Daten aus Dictionary zu finden, bzw. zu erzeugen...
 // Code hier nicht aufgeführt ...
}

Auf diese Art und Weise kann unabhängig von manuellen Erweiterungen der Klassen einfach mittels Core Data neu aus dem Model generiert werden, ohne den händisch eingefügten Code zu verlieren.
Zurück zum Thema Asynchronität und nicht threadsafer ManagedObjectContext. Ein übliches Szenario besteht darin, dass ein Thread im Hintergrund Daten synchronisiert und mit einer Core-Data-Datenbank abgleicht, während der UI Thread auf Daten aus Core Data zugreift und sie anzeigt. Beide Threads sollten dabei unterschiedliche Instanzen des ManagedObjectContext verwenden. Üblicherweise gibt man den ViewControllern mit einer initWithManagedObjectContext Methode eine eigene Instanz für den Datenzugriff zur Anzeige mit. Um die beiden Kontexte nun zu synchronisieren, kann man auf NSNotifications zurückgreifen. Dazu meldet sich ein Objekt bzw. der lesende ManagedObjectContext als Observer des schreibenden ManagedObjectContext an. Sobald der schreibende Kontext den Objektgraphen manipuliert, bzw. speichert,wird eine Notification ausgelöst und auf dem lesenden Kontext ein Callback aufgerufen, in dem dann beispielsweise ein [tableview reloadData] aufgerufen werden kann. Listing 6 zeigt, wie die Anmeldung der Notifications und die Zusammenarbeit mit einem Hintergrundprozess (realisiert mit GCD) aussehen können.

// Beispiel: Aktivierung der App started Hintergrund Sync und notifiziert 
// lesenden ManagedObjectContext wenn fertig. 
- (void)applicationDidBecomeActive:(UIApplication *)application
{
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    NSManagedObjectContext *_observer = self.managedObjectContext;

    [center addObserver:_observer 
               selector:@selector(mergeChangesFromContextDidSaveNotification:) 
                   name:NSManagedObjectContextDidSaveNotification
                 object:self.managedObjectContextSync];
    
    dispatch_queue_t queue = dispatch_queue_create("background.sync.queue", NULL);
    
    DataSyncManager *_syncManager = self.syncManager;
    dispatch_async(queue, ^{
        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
        [_syncManager syncData];
        [center removeObserver:_observer];
        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
    });
    
    dispatch_release(queue

Im vorherigen Beispiel werden die ManagedObjectContexts selbst über Notifications verknüpft. Das ist in diesem Beispiel sinnvoll, da der lesende Kontext die Daten mittels FetchResultsController anzeigt, der wiederum auf Basis des Callbacks, die dann der lesende Kontext nach der Notification aufruft, einen Callback Hook anbietet, in dem zum Beispiel die TableView aktualisiert werden kann. Das nächste Beispiel zeigt den Callback, der durch das NSFetchResultsControllerDelegate zur Verfügung gestellt wird, um auf Datenänderungen in Core Data zu reagieren:

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self startRefreshIndicator];
    [self.tableView reloadData];
    [self stopRefreshIndicator];
}

Die Klasse NSFetchResultsController vollständig zu beschreiben, würde den Rahmen des Artikels sprengen, jedoch handelt es sich dabei absolut um ein „Essential“, wenn man mit Core Data arbeitet und den Datenzugriff, die Zusammenarbeit bzw. Darstellung von Tabellen mit und ohne Sections sowie Indexen und Suchfunktionalität stark vereinfachen und umsetzen will. Das grundsätzliche Prinzip funktioniert folgendermaßen: Man formuliert einen NSFetchRequest, was einer Abfrage in der Datenbank gleichkommt. Definiert wird dabei die Entität, die man erhalten will. Mit NSPredicats (Prädikatenlogik) werden Bedingungen an die zu erhaltenden Daten sowie Sortierungsvorgaben spezifiziert. Dieser Request wird dann gegen Core Data abgeschickt und liefert die passenden Daten zurück. Der NSFetchResultsController verbindet dabei den FetchRequest mit einem TableViewController und ermöglicht ein einfaches Abfragen der notwendigen Informationen für die Darstellung von Tabellen (z. B. Anzahl an Sections, Infos, um einen Navigationsindex zu erstellen, Anzahl der Rows innerhalb einer Section, …). Dabei stellt der NSFetchResultsController, wie bereits erwähnt, über das NSFetchResultsControllerDelegate auch verschiedene Callbacks zur Verfügung, um auf Datenänderungen zu reagieren. Listing 7 illustriert den Einsatz des NSFetchResultsController innerhalb eines TableViewControllers.

// Beispiel: Initialisierung eines NSFetchResultsController innerhalb eines
// TableView Controllers. 
- (NSFetchedResultsController *)fetchedResultsController {
    // Careful: NO "self." here! 
    if (!fetchedResultsController) {
        
        NSManagedObjectContext *_context = self.managedObjectContext;
        
        NSFetchRequest *request = [[NSFetchRequest alloc] init];
        request.entity = [NSEntityDescription entityForName:@"Speaker" 
                                     inManagedObjectContext:_context];
        request.fetchBatchSize = 20;
        
        NSSortDescriptor *lastNameDescriptor = [[NSSortDescriptor alloc] 
                                  initWithKey:@"lastName" ascending:YES];
        NSSortDescriptor *firstNameDescriptor = [[NSSortDescriptor alloc] 
                                  initWithKey:@"firstName" ascending:YES];
        [request setSortDescriptors:[NSArray arrayWithObjects:lastNameDescriptor, 
                                                         firstNameDescriptor, nil]];
        
  [lastNameDescriptor release];
  [firstNameDescriptor release];
        
  NSFetchedResultsController *newController =
                   [[NSFetchedResultsController alloc] initWithFetchRequest:request
                                    managedObjectContext:_context
                                      sectionNameKeyPath:@"lastName"
                                               cacheName:@"SpeakerListCache"];
        newController.delegate = self;
        self.fetchedResultsController = newController; 
        
        [newController release]; 
        [request release];
    } 
    
    // Careful: NO "self." here! 
    return fetchedResultsController;
}

// Beispiel: Auslösen des performFetch Request aus viewDidLoad
- (void)viewDidLoad {
    [super viewDidLoad];
    [self startRefreshIndicator];

    NSError *anyError = nil;
    BOOL success = [self.fetchedResultsController performFetch:&anyError];
    
    if (!success) {
        NSLog(@"Error fetching data! Error = %@", anyError);
        UIAlertView *alert = 
               [[UIAlertView alloc] initWithTitle:@"Error" 
                                          message:@"Could not refresh data!" 
                                         delegate:nil 
                                 cancelButtonTitle:@"Cancel" 
                                  otherButtonTitles:nil, nil];
        
;
; } [self stopRefreshIndicator]; }

Mit den von iOS bereits zur Verfügung gestellten Frameworks und Bibliotheken kommt man schon recht weit, man kann auf deren Basis so ziemlich alle Anforderungen selbst umsetzen. Allerdings ist es wie in jeder Programmiersprache und jeder Plattform notwendig und sinnvoll, auf externe Frameworks zuzugreifen, um die Produktivität und Effizienz bei der Entwicklung zu steigern.

[ header = Externe Frameworks und Bibliotheken ]

Im Rahmen des zu Beginn definierten Kontexts an eine Mobile-Anwendung werden in diesem Abschnitt ein paar unserer Meinung nach essenziellen externen Bibliotheken vorgestellt.

Bei dem Framework ASIHTTPRequest handelt es sich um einen einfach verwendbaren Wrapper, um das CFNetwork API, der die Kommunikation mit einem Webserver vereinfacht. Es handelt sich dabei um eine Objective-C-Bibliothek, die sowohl für Mac OS X als auch das iPhone/iPad genutzt werden kann. ASIHTTPRequest eignet sich besonders gut, um HTTP-Anfragen und Interaktionen mit REST-basierten Diensten (GET, POST, PUT, DELETE) zu realisieren. Ebenfalls enthalten ist eine Subklasse namens ASIFormDataRequest, die das Versenden von POST-Daten und Dateien mithilfe von multipart/formdata Requests sehr komfortabel ermöglicht. Es werden  synchrone und asynchrone Anfragen unterstützt. Letztere auch seit Neuestem mithilfe eines API, das auf Blocks basiert (Listing 8).

- (void)getDataAsync { 
  NSURL *url = [NSURL URLWithString:@"http://myServer.com"];
  __block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
  [request setCompletionBlock:^{
    // Use responseString when fetching text data
    NSString *responseString = [request responseString];
    // Use responseData when fetching binary 
    NSData *responseData = [request responseData];
  }];

[request setFailedBlock:^{
    NSError *error = [request error];
  }];

  [request startAsynchronous];
}

Will man während des Downloads eine Statusanzeige aktualisieren, kann die Methode [request setDownloadProgressDelegate:myProgressIndicator] verwendet werden, um eine UIProgressView mit aktuellen Daten zu versorgen. Seit iOS4 ist ebenfalls die Möglichkeit hinzugekommen, mit [request setShouldContinueWhenAppEntersBackground:YES] Requests fortzusetzen, auch wenn die App in den Hintergrund gelegt wird. Ein weiteres erwähnenswertes Feature ist das Streamen von Daten direkt in ein File, was bei größeren Datenmengen sehr hilfreich sein kann.

ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url]; 
[request setDownloadDestinationPath:@"<path>/downloaded.file"];

Bei Verbindungsabbrüchen kann durch [request setAllowResumeForFileDownloads:YES] ein abgebrochener Download (in ein File) sogar wieder weitergeführt werden. Es gibt für den Upload (ASIFormDataRequest) einen analogen Weg, um POST-Daten aus einem File zu streamen. Das kann mittels [request setShouldStreamPostDataFromDisk:YES] und [request appendPostDataFromFile:@“<path>/bigfile“] erreicht werden. Weitere Funktionen von ASIHTTPRequest, die einem Entwickler viel Arbeit abnehmen können sind die Unterstützung für Authentifizierung (auch mit dem Verwalten von Passwörtern in der Keychain und vorgefertigtem Login-Dialog) und das Handling von Zertifikaten sowie GZIP-Support und die Unterstützung von Persistent Connections. Um überhaupt sicherzustellen, dass man auf einen Server zugreifen kann, bzw. sich in einem funktionierenden Netzwerk befindet, kann das Reachability-Codebeispiel von Apple als Blaupause dienen.

Die Reachability-Beispielapplikation zeigt die Nutzung des SystemConfiguration-Frameworks, um den Netzwerkzustand zu ermitteln und zu überwachen. Speziell wird damit ermittelt, wann und ob ein Netzwerk oder Serverdienst verfügbar ist und ob der Netzwerkverkehr über ein Wireless Wide Area Network (WWAN) wie EDGE oder 3G geleitet wird. Man kann die Reachability-Klassen einfach als Framework in der eigenen App einbinden und wiederverwenden. Das Framework erlaubt es, sich für Notifications anzumelden, die über die entsprechenden Statusänderungen informieren. Überwachungsmöglichkeiten des APIs zeigt Listing 9.

//reachabilityWithHostName- Erreichbarkeit eines speziellen Hostnamen prüfen. 
+ (Reachability*) reachabilityWithHostName: (NSString*) hostName;

//reachabilityWithAddress- ERreichbarkeit eine IP Adresse prüfen. 
+ (Reachability*) reachabilityWithAddress: (const struct sockaddr_in*) hostAddress;

//reachabilityForInternetConnection- Prüft ob eine Default-Route ins Internet
// verfügbar ist. Nütlich für Apps, die zu keinem speziellen Host verbinden. 
+ (Reachability*) reachabilityForInternetConnection;

//reachabilityForLocalWiFi- Prüft die Verfügbarkeit von lokalem WiFi
+ (Reachability*) reachabilityForLocalWiFi;

//Started das “Listening” für Reachability Notifications in aktueller Run-Loop
- (BOOL) startNotifier;
- (void) stopNotifier;
- (NetworkStatus) currentReachabilityStatus;

Die Nutzung des Frameworks wird in Listing 10 aus dem Apple-Beispiel skizziert.

// Beobachte kNetworkReachabilityChangedNotification.  Wenn diese Notification 
// versendet wird, wird die Methode  "reachabilityChanged" aufgerufen. 
 [[NSNotificationCenter defaultCenter] addObserver: self 
                                          selector: @selector(reachabilityChanged:) 
                                              name: kReachabilityChangedNotification 
                                            object: nil];
remoteHostLabel.text = [NSString stringWithFormat: @"Remote Host: %@", @"www.myServer.com"];
hostReach = [[Reachability reachabilityWithHostName: @"www.myServer.com"] retain];
[hostReach startNotifier];

Wie in der Einleitung des Artikels schon angekündigt, wollen wir auch nützliche Ergänzungen für das UI einer App vorstellen.

[ header = OpenFlow ]

Eines der iPhone/iPod UI Controls ist der Coverflow zum schnellen Browsen durch CD-Cover bzw. Bilder. Leider ist diese Komponente nicht Bestandteil des öffentlichen iOS SDK. Zum Glück kann man hier aber auf das Open-Source-Framework OpenFlow zurückgreifen, was die Nutzung eines Coverflows in der eigenen App sehr einfach macht. OpenFlow liefert ein Beispielprogramm mit, das die unterschiedlichen Möglichkeiten beschreibt, wie man das UI Control mit Bildern befüttern kann. Es gibt insgesamt zwei Varianten:

1. Alle Bilder auf einmal laden und setzen
2. Nutzung einer Operation Queue zum asynchronen Laden der Bilder

Als einfaches Beispiel sieht man im Folgenden den Code, der notwendig ist, um die View (AFOpenFlowView *myOpenFlowView) mit Bildern zu parametrisieren. Die View kann man in gewohnter Art und Weise der Window oder einer Container View als Subview hinzufügen (Listing 11).

-(void) viewDidLoad {
  [myOpenFlowView setNumberOfImages:5];

  for (int index = 0; index < 5; index++){
    NSString *imageName = [NSString stringWithFormat:@"%d.png",index];
    UIImage *image = [UIImage imageNamed:imageName];
    [myOpenFlowView setImage:image forIndex:index];
  }
}

In diesem Beispiel liegen die Bilder im Bundle der App in nummerierter Form vor und werden mittels setImage :forIndex gesetzt. Natürlich lassen sich die Bilder auch aus anderen Quellen beziehen. Aber hier sollte man sich einfach die von OpenFlow mitgelieferte Demoanwendung anschauen (Abb. 3).

Abb. 3: OpenFlow: Open Source Coverflow Framework

Zuletzt muss unbedingt das sehr mächtige iOS-Framework Three20 genannt werden. Es handelt sich um eine Open-Source-Objective-C-Bibliothek, die von Dutzenden bekannter iOS Apps verwendet wird. Dazu zählen u. a. Facebook, Posterous, Pulse oder Meetup.com. Mit Three20 werden mächtige ViewController wie ein Launcher (Dashboard), ein Fotobrowser, sowie „internet-aware“-Tabellen zur Verfügung gestellt. Durch den modularen Aufbau der Bibliothek kann man für die eigene App selbst auswählen, welche Teile von Three20 man verwenden will. Zu erwähnen wären noch Features wie „gestylte View und Labels“ (z. B. Textfields, Buttons), eine „Drag Refresh“-Funktion für das Aktualisieren von Tabellen und zusätzliche Tab Controls (Abb. 4).

Abb. 4: Three20 UI Control für „Tabelle herunterziehen, um Daten zu aktualisieren“

Im Folgenden erläutern wir den Launcher (Abb. 5) und das Navigationsframework.

Abb. 5: Three20: Dashboard-Ansicht mittels Launcher ViewController

Bei dem Launcher handelt es sich um eine View und einen ViewController (TTLauncherView, TTLauncherViewController), um eine Dashboard-Ansicht zu realisieren. Dabei können Launch-Icons in einem Raster über mehrere Bildschirme verteilt und umsortiert werden. Jedes dieser Icons kann wiederum eine Badge anzeigen. Das Prinzip entspricht dem iPhone-„Homescreen“. Der einfachste Weg, um den TTLauncherView zu verwenden, ist es, den TTLauncherViewController zu verwenden. Dieser ViewController erledigt die Vorkonfiguration und das Management der View und erlaubt es dem Entwickler, sich darauf zu konzentrieren, Inhalt hinzuzufügen. Listing 12 zeigt ein Beispiel für einen von TTLauncherViewController abgeleiteten ViewController, der der View (launcherView) Launch Icons hinzufügt.

@implementation LauncherViewController // inherits from TTLauncherViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    TTLauncherItem* item = [[TTLauncherItem alloc] initWithTitle: @"Item name"
                                                   image:@"bundle://Icon1.png"
                                                     URL: nil];
    [self.launcherView addItem:item animated:NO];

    TT_RELEASE_SAFELY(item);
}
@end

Was auffällt, ist der URL, den man einem TTLauncherItem mitgeben kann. Das bringt uns auch direkt zu dem nächsten Thema, nämlich dem TTNavigator und der URL-basierten Navigation in Three20. Das Konzept hinter dem TTNavigator ist es, URL-Patterns mit ViewControllern in einer URL-Map zu assoziieren. Der Aufrufer fragt beim TTNavigator einen URL, diese findet den entsprechenden ViewController und öffnet ihn per TTURLAction. Es ist üblich, einen einzigen „geshareten“ Navigator in einer App zu verwenden. Listing 13 zeigt, wie die Verwendung typischerweise aussieht.

// Beispiel: Registrieren eines ViewController mit einer URL
TTNavigator* navigator = [TTNavigator navigator];
navigator.window = window;

TTURLMap* map = navigator.URLMap;
[map                  from:@"tt://contact/(initWithName:)" 
          toViewController:[ContactController class]];


// Beispiel: Aufruf eines ViewControllers mittels TTNavigator
 [[TTNavigator navigator] openURLAction: [[TTURLAction
                     actionWithURLPath:@"tt://contact/Peter"] 
                         applyAnimated:YES]];

In diesem Beispiel wird beim openURLAction eine Instanz des ContactController mit initWithName erzeugt. Als Parameter wird dabei „Peter“ übergeben. Dieses Pattern ist deutlich verständlicher und einfacher zu handhaben als der Standardweg über das Erzeugen/Alloc/Init des „Ziel-ViewControllers“ und „Pushen“ auf den Navigation-Stack und ähnelt etwas dem Handling von Hyperlinks in HTML bzw. Actions aus Webframeworks wie Struts. Ein großer Vorteil dieses Mechanismus ist, dass der komplette Navigationszustand einer App über URLs abgebildet und gemerkt und somit auch wiederhergestellt werden kann. Three20 stellt sicher eines der umfangreichsten und mächtigsten Frameworks für iOS dar und es kann nicht schaden, einen Blick in die Three20-Bibliotheken zu werfen, falls man etwas bauen will, was man in den großen „State-of-the-Art“-Apps gesehen hat, da die Chance sehr groß ist, dass Three20 dabei im Spiel gewesen ist.

[ header = Werkzeuge & Instruments ]

Besonders im Umfeld von C und dynamischen Sprachen ist ein gutes Tooling nahezu unumgänglich. Sicher ist es auch möglich, gute Apps ohne die nachfolgenden Werkzeuge zu schreiben, allerdings wird die Qualität oder eben die Entwicklungsdauer darunter leiden. Im Folgenden werden wir uns die wichtigsten drei Werkzeuge bei der iOS-Entwicklung etwas näher anschauen. Zwei der genannten dürften sicherlich für den einen oder anderen Leser noch neu sein, da sie erst mit Xcode 4.2 ausgeliefert werden. Genau deswegen schauen wir uns diese an, denn sie bringen ein ganzes Stück Arbeitserleichterung.

Instruments ist eine Anwendung, die im SDK-Auslieferungsumfang enthalten ist und es ermöglicht, dynamisch iOS-Code zu profilen und zu tracen. Mittels Instruments ist es möglich, die unterschiedlichen Aspekte eines Prozesses zu verfolgen und zu analysieren. In der Java-Welt würde man wohl das Tool „jvisualvm“ ein Stück weit mit Instruments vergleichen, auch wenn es bei Weitem nicht den Umfang an Features, hier Instruments genannt, bietet. Die wichtigsten Einsatzgebiete von Instruments sind:

  • Schwer zu findende/reproduzierbare Fehler aufspüren
  • Performanceanalyse
  • Memory Leaks aufspüren
  • Automatisches Testen
  • Erlangen eines besseren Verständnis der Applikation

Wenn man Instruments öffnet, wird ein so genanntes „Trace-Document“ angelegt. Dieses ist quasi die Klammer über die möglichen Module, die man für die Analyse verwenden kann. Von Haus aus bietet das Werkzeug vorgefertigte Templates, die für unterschiedliche Vorhaben die sinnvollsten Module beinhalten. Die wohl am meisten genutzten Templates sind Allocations, Core Data, Leaks, Threads sowie das Zombie Template. Was diese Templates im Detail bieten, kann sehr gut in der Apple-Dokumentation unter „Instruments User Guide“ nachgeschlagen werden. Generell muss man sagen, dass sich immer ein Blick in die sehr gute Apple-Dokumentation lohnt. Hat man eine klare Vorstellung, welche Daten für die Analyse der App benötigt werden, sind es nur drei Schritte, bis die Daten analysiert werden können:

  1. Trace Document mittels Template erstellen
  2. Target definieren
  3. Daten aufnehmen

Nachdem die Daten aufgenommen wurden, bietet Instruments unterschiedliche Ansichten für die Auswertung. Abbildung 6 bietet einen kleinen Einblick in Instruments.

Abb. 6: Monitoring- und Profiling-Werkzeug aus iOS SDK

Im Allgemeinen muss man sagen, dass Instruments einfach zu der täglichen iOS-Entwicklung mit dazugehört. Auch für erfahrene Entwickler ist es immer wieder überraschend, wie viel Performance sowie diverse weitere Verbesserungen mittels gezieltem Profilen zu erreichen ist. Starten wir mit dem neuen Feature Automatic Reference Counting (ARC).

Alle iOS-Entwickler kennen das leidige Thema Memory Management. iOS bietet kein automatisches Memory Management, wie man es etwa von Java und dem Garbage Collector her kennt. Somit muss sich der Entwickler selbst um das Memory Management kümmern und die bekannten Regeln anwenden, damit keine Memory Leaks in die App eingebaut werden. Genau hier wird mit Xcode 4.2 und dem neuen LLVM 3.0 Compiler Abhilfe geschaffen. Das Feature Automatic Reference Counting (ARC) kümmert sich um das Memory Management für Objective-C-Objekte. ARC ist allerdings kein Garbage Collector zur Laufzeit sondern ein Compiler Feature. Beim Kompilieren von Sourcecode webt der Compiler automatisch die release– und retain-Statements mit in den Code ein.

Projekte, die mit einem alten Compiler, z. B. dem LLVM-GCC erstellt wurden, können mittels Xcode 4.2 konvertiert werden. Hierzu gibt es einen Menüeintrag „Convert to Objective-C ARC“ in Xcode 4.2. Zum Schluss noch der Hinweis, dass es Elemente gibt, die nicht automatisch konvertiert werden können. Xcode zeigt diese an, der Entwickler muss sie manuell konvertieren. Natürlich ist ARC für gestandene Objective-C-Entwickler eine Umstellung, allerdings wird man diese Umstellung gerne in Kauf nehmen.

Das letzte Feature, das wir uns anschauen wollen, ist das Storyboard. Es bietet die Möglichkeit, auf grafische Art und Weise die Navigation in der App zwischen den Views zu erstellen. Es ist Bestandteil des integrierten Interface Builder und bietet neben den neuen Storyboard-Funktionen den gleichen Funktionsumfang wie der alte Interface Builder. Noch ohne Storyboard musste ein iOS-Entwickler die eine oder andere Zeile Code für die Navigation der App schreiben. Das kann jetzt in einer übersichtlichen Darstellung sehr komfortabel grafisch erledigt werden. Nichtsdestotrotz gibt es die Möglichkeit, auch weiterhin im Sourcecode die Navigation einzubauen oder zu beeinflussen. Hierzu gibt es unter anderem die Option, mittels der Methode performSegueWithIdentifier:sender: direkt einen Übergang aufzurufen. In Abbildung 7 kann man ein Storyboard mit diversen Szenen erkennen

Abb. 7: XCode 4.2 Storyboard

Will man die Navigation und die Transitions der Übergänge seiner App mittels Storyboard erstellen, wird mit so genannten Szenen gearbeitet. Auf dem iPhone wird eine Szene durch einen ViewController representiert, beim iPad kann ein Screen mit mehreren Szenen bestückt sein. Welche Transition bei einem Übergang von einer Szene zur nächsten verwendet wird, kann bei den Merkmalen eines Übergangs (Verbindungen im Screenshot) ausgewählt werden. Hier befinden sich die bekannten Typen wie „Push“, „Modal“ oder eben auch „Custom“. Bei der Einstellung „Custom“ muss eine eigene Implementierung hinterlegt werden. Im Allgemeinen ist das Storyboard-Feature eine sehr angenehme Sache, jedoch hat man bei kleinen Bildschirmen ein Platzproblem und sollte die Szenen auf mehrere Storyboard Files aufteilen. Arbeitet man an einem großen Bildschirm, ist das Feature ein echter Gewinn und zugleich Eyecatcher.

Abgesehen von den drei genannten Werkzeugen oder auch Features werden mit Xcode 4.2 noch einige weitere Neuerungen ausgeliefert. Diese konnten wir aufgrund der Länge des Artikels hier nicht alle behandeln, sie sollen aber der Vollständigkeit wegen genannt werden. Zu den neuen Features gehören noch OpenGL ES Frame Capture, das sehr nützliche Location-Simulation-Feature sowie das Application-Data-Management-Feature. Die in diesem Artikel vorgestellten Frameworks und Muster können nur als Einstieg und Übersicht dienen, aber es gibt darüber hinaus noch viel zu entdecken.

[ header = Nützliche externe Frameworks & Fazit ]

Fazit

Um effizient und produktiv iOS-Anwendungen zu entwickeln kann man auf viele Werkzeuge und Hilfsmittel aus dem iOS SDK zurückgreifen. Damit lassen sich auch anspruchsvolle Apps sehr gut umsetzen. Die MVC-Grundkonzepte sowie alle Kernmuster für die Realisierung „flüssiger“ Apps (Async Processing, Multi-Threading, Persistenz etc.) sind durch die Bordmittel des iOS SDK bestens abgedeckt. Themen wie Blocks oder GCD gehören dafür allerdings zum Handwerkszeug, das man sicher beherrschen sollte. Will man jedoch mit der eigenen App aus der Masse herausstechen oder spezielle Anforderungen effizienter, schneller und kostensparend umsetzen, sollte man einen Blick auf die vorgestellten externen Frameworks werfen. Das gilt insbesondere für das Individualisieren und Optimieren von User Interfaces durch Frameworks wie Three20. Ein wichtiger Effekt beim Befassen mit den vorgestellten Themen ist, dass man mit typischen Anforderungen an mobile Apps konfrontiert wird, die so in einem anderen Umfeld, wie z. B. RCP, Client-/Server oder Webapplikationen weniger prominent sind.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -