XCode – Nachträgliche Konfiguration von Application Tests

Beim Anlegen eines neuen Projektes kann XCode 4.3 auf Wunsch ein Unit-Test Bundle erzeugen. Die Option dazu heisst Include Unit Tests. Die so angelegten Unit-Tests werden in der Menüleiste unter Product > Test oder alternativ über den Shortcut cmd-U angestossen. XCode startet die zu testende App im Simulator und führt die Unit-Tests aus. Apple nennt diese Form der Tests Application Tests; ich würde sie eher Integration Tests nennen. Um eine ältere App nachträglich mit Application Tests zu versehen, müssen einige manuelle Schritte ausgeführt werden. Als Beispiel soll eine App namens AppWithoutTests dienen, die ich ohne die Include Unit Tests-Option angelegt habe.

1. Erzeugen eines Cocoa Touch Unit Testing Bundles

  • Klick auf File > New > Target ...
  • Auswahl des Cocoa Touch Unit Testing Bundle
  • Eingabe eines Product Names (z.B. Application Tests) und Klick auf Finish

2. Aufräumen der Schemes
XCode hat nun das Testing Bundle erzeugt, jedoch hat es ein weiteres Scheme angelegt. Für die Entwicklung ist das umständlich, da man für die Ausführung der Tests immer das Scheme wechseln muss. Von daher löschen wir das neu angelegte Scheme und erweitern das bisherige Standard-Scheme.

  • Klick auf Product > Manage Schemes ...
  • Löschen des automatisch angelegten Scheme ApplicationTests
  • Hinzufügen des ApplicationTests Bundles im Test-Target des Standard-Schemes (im Beispiel AppWithoutTests).

3. Testing Bundle: Bundle Loader und Testhost
Noch sind wir nicht fertig. Wir müssen die Wirts-Applikation in den Bundle Settings unseres Testing Bundles selbst konfigurieren.

  • Zunächst setzen wir den Pfad des Bundle Loader auf die unsere kompilierte App, also $(BUILT_PRODUCTS_DIR)/<app_name>.app/<app_name>. Für unsere Beispielapp müssen wir den Pfad auf $(BUILT_PRODUCTS_DIR)/AppWithoutTests.app/AppWithoutTests setzen.
  • Anschliessend setzen wir den Test Host auf den soeben konfigurierten Bundle Loaders. Dazu geben verwenden wir die Variable $(BUNDLE_LOADER).

4. Testing Bundle: Setzen der Target Depedencies
Die Abhängigkeit unsers Testing Bundles zu dem Application Target müssen wir selbst hinzufügen, so dass sichergestellt ist, dass immer zuerst die App, und dann die Tests kompiliert werden.

  • Klick auf die Build Phases unseres Testing Bundles
  • Klick auf Plus-Symbol in der Sektion Target Dependencies
  • Auswahl des Application Targets (Im Beispiel AppWithoutTests).

5. Application Target: Symbols hidden by default abschalten
Die zu testende Applikation darf die kompilierten Methoden und Klassen nicht verstecken. Ansonsten scheitert der Linker im Testing Bundle, weil er die importierten Klassen nicht findet.

  • Dazu müssen wir sicherstellen, dass die Option Symbols Hidden by Default in unserem Application Target AppWithoutTests” auf NO gesetzt ist.

Mit diesen (leider sehr umständlichen) Schritten können nun Application Tests für unsere AppWithoutTests ausgeführt werden. Im nächsten Blogeintrag geht es um die so genannten “Logic Tests”, also Tests, die keine Wirts-Applikation benötigen.

HMLauncherView

Letzte Woche habe ich meine iOS-Komponente “HMLauncherView” auf Github hochgeladen. Die Komponente bietet eine Alternative zu der TTLauncherView aus dem Three20-Framework. Das Besondere ist, dass auch mehrere “LauncherViews” interagieren können. So können Buttons von einer Liste zur anderen verschoben werden (Siehe Video).

Ausbaufähig ist noch die Dokumentation, aber das kommt auch noch.

Link zu Github: https://github.com/heikomaass/HMLauncherView

Erzeuge keine UIViews in der init-Methode eines UIViewControllers

Vor einiger Zeit hatte ich bei einem Code-Review folgende init-Methode eines UIViewControllers gesehen:

- (id) init {
	if (self = [super init]) {
		UIImage *image = [UIImage imageNamed:@"someimage.png"];
		UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
		self.logoImageView = imageView;
		[imageView release];
	}
	return self;
}

- (void) dealloc {
   [logoImageView release];
   [super dealloc];
}

Was ist daran falsch ? Bevor ich die Frage beantworte, möchte ich den Lifecycle eines UIViewController-Objekts stark vereinfacht vorstellen:

Lifecycle

Der Lifecycle eines UIViewControllers umfasst folgende Methodenaufrufe:

  1. init
  2. loadView
  3. viewDidLoad
  4. (Optional) viewDidUnload
  5. dealloc

Die init-Methode (1.) wird nur 1x im Leben eines UIViewController-Objekts aufgerufen. Sobald das erste Mal auf das view-Property eines UIViewControllers zugegriffen wird, ruft UIKit die loadView-Methode (2.) auf, um die View für den Controller zu erzeugen. Anschliessend ruft UIKit die Methode viewDidLoad (3.) auf. Bei Speichermangel (Memory Warning) reagiert der UIViewController und führt die Methode viewDidUnload (4.) aus, sofern die View nicht gerade sichtbar ist. Zitat:

When a low-memory condition occurs and the current view controller’s views are not needed, the system may opt to remove those views from memory.
[…] If your view controller stores references to the view or its subviews, you should use this method to release those references
Dokumentation viewDidUnload

Wenn die View des Controllers durch viewDidUnload abgeräumt wurde, dann muss sie natürlich beim nächsten Zugriff wieder erstellt werden. In diesem Fall wird loadView und viewDidLoad erneut aufgerufen. Dieser Zyklus kann sich beliebig häufig wiederholen.

Keine UIViews in init erzeugen

So. Zurück zur Anfangsfrage: Warum sollte man keine UIViews in der init-Methode anlegen ? Der Grund ist einfach: Sie können so bei Speichermangel nicht abgeräumt werden. Wenn man beispielsweise eine UIImageView im init erzeugen und in viewDidUnload releasen würde, dann würde sie nie wieder neu erzeugt werden, da init ja nur 1x aufgerufen wird. Das Resultat sind fehlende Views bei Speichermangel.

Kurzum: UIViews sollen nie im init, sondern immer nur im loadView oder in viewDidLoad angelegt werden.

Das iOS-Framework “Three20” in der Retrospektive

Vor fast zwei Jahren stand ich vor dem Problem, eine Schnellstart-Buttonleiste in eine iPhone-App einzubauen. Die Buttons in der Leiste sollten analog zum iPhone-Homescreen mittels Drag&Drop sortiert werden können.

Bei der Recherche nach einer bereits fertigen Open Source-Komponente bin ich auf das
Three20-Framework gestossen, dass damals von vielen Projekten eingesetzt wurde. Auf der Webseite von Three20 wird der eigene Launcher als wichtiges Feature angeteasert.

Three20 wurde von Joe Hewitt ursprünglich für die Facebook-iPhone-App entwickelt. Joe kommt aus der “Webwelt” und hat vorher den äusserst nützlichen Firebug ins Leben gerufen. Mit Three20 hat er versucht, Ideen aus der Webentwicklung in die iOS-Welt zu übertragen. Beispielsweise werden die UIViewController mit URLs versehen, die über einen eigenen Dispatcher (TTNavigator) aufgerufen werden. Zudem können UI-Elemente ähnlich wie CSS über einen Stylesheet (TTStyleSheet) angepasst werden.

Mir waren die (zugegeben interessanten) Ansätze jedoch viel zu invasiv.
Der TTNavigator und die Stylesheets verstehen sich quasi als “Gegen”-Framework zum UIKit,
und das kann auf Dauer nicht funktionieren. Ich hatte damals lediglich die TTLauncherView-Klasse in mein Projekt integriert.

Soweit so schön. Jetzt kommt jedoch der Haken der Story:

Bei jedem iOS-Update hatte ich Probleme mit Three20, da das Framework erst an Apple’s Änderungen angepasst werden musste. Auch beim Upgrade von XCode 3 auf XCode 4 kam es aufgrund des komplexen, intranparenten Buildsystems von Three20 zu Komplikationen. Joe Hewitt hatte mittlerweile Facebook verlassen, und der neue Projektverantwortliche musste dann einen Monat später leider folgendes Fazit über Three20 ziehen:

  • Poor documentation.
  • Spaghetti dependencies.
  • Suffering from a “kitchen sink” complex.
  • A complex build structure.
  • An enormous number of difficult-to-solve bugs.
  • Next-to-zero test coverage.

Quelle: https://github.com/jverkoey/nimbus (Absatz “Nimbus Background”)

Aus diesen Gründen gibts nun das Nachfolger-Framework “Nimbus” (https://github.com/jverkoey/nimbus). Leider wird dies auf der Three20-Projektwebseite nicht erwähnt. Von daher die Warnung auf dieser Seite: Don’t use it!

Vor knapp vier Monaten stand ich erneut vor dem “Problem”, eine Schnellstart-Buttonleiste in eine iOS-App zu integrieren. Diesmal habe ich es komplett selber programmiert.

Objective-C. Ein paar Unterschiede zu Java

Mit der steigenden Popularität von Apple-Produkten rückte auch die Programmiersprache “Objective-C” in den Fokus von Software-Entwicklern, da nur mit ihr die Entwicklung von nativen iPod-, iPhone- und iPad-Applikationen möglich ist. Das Faszinierende an dieser Sprache ist die Kombination aus statischer und dynamischer Typisierung:

“Objects are dynamically typed. In source code (at compile time), any object variable can be of type id no matter what the object’s class is. The exact class of an id variable (and therefore its particular methods and data structure) isn’t determined until the program runs. […] To permit better compile-time type checking, and to make code more self-documenting, Objective-C allows objects to be statically typed with a class name rather than generically typed as id. It also lets you turn some of its object-oriented features off in order to shift operations from runtime back to compile time.”

http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocStaticBehavior.html

Konkret bedeutet dies, das beliebige Nachrichten an Objekte verschickt werden können. Mit Hilfe von “Categories” können bestehende Klassen (beispielsweise NSString) sogar um neue Methoden erweitert werden, ohne deren Quellcode zu ändern. Zudem können größere Klassen mit Categories in mehrere Dateien aufgeteilt werden.

Aufgrund des dynamischen Verhaltens kennt Objective-C keine privaten Methoden. Zwar kann auf eine Deklaration der Methode im Interface verzichtet werden, allerdings kann die Methode dennoch aufgerufen werden.

Auch null-Referenzen verhalten sich etwas anders als gewohnt: In Java führt jeder Methodenaufruf einer null-Referenz zwangsläufig zu einer NullPointerException. In Objective-C dagegen wird in diesem Fall nil zurückgegeben, und das ohne Fehlermeldung. Die nil-Referenz hat auch eine weitere Aufgabe: In Listen (NSMutableArray) wird nil zur Markierung des Listenendes verwendet, und kann somit nicht als null-Wert genutzt werden. Für diesen Zweck gibt einen eigenen Typ namens NSNull.

Eine Garbage Collection ist nur für Mac OSX 10.5 verfügbar. iPad-, und iPhone-Entwickler müssen sich dagegen mit dem halbautomatischen Memory-Management anfreunden bzw. auseinandersetzen. Da Objective-C direkt von C ableitet, erbt die Sprache auch einige C-Artefakte wie beispielsweise den für Java-Entwickler ungewohnten Präprozessor.

Trotz dieser Hürden überzeugt Objective-C jedoch mit ihrer spannenden Kombination aus statischer Typisierung und dynamischen Binden. Das Tooling (XCode) kann leider aber noch nicht mit Eclipse mithalten. Hier besteht noch deutlicher Nachholbedarf seitens Apple.

Top-7 der XCode-Shortcuts

[Novum heute: Der erste Blogeintrag zu Objective-C bzw. XCode.]
XCode ist Apple’s IDE für die Entwicklung von iPhone und Mac-Applikationen. Allerdings ist XCode bei weitem nicht so komfortabel und mächtig wie Eclipse. Dennoch gibt es ein paar Shortcuts, die jeder iPhone-Entwickler kennen sollte:

1. cmd-shift-d (Springt schnell zu einem File, ähnlich ctrl-shift-r in Eclipse )
2. cmd-alt-up (Wechselt von .h Datei zur passenden .m Datei und umgekehrt)
3. cmd-shift-j (Refactor von Klassen, Methoden und Variablen)
4. ctrl-shift-7 (Springt zum nächsten Platzhalter)
5. cmd-shift-7 (Kommentiert markierten Code)
6. ctrl-k (Löscht aktuelle Zeile)
7. cmd-shift-e (Schaltet den Editor in der Projektansicht ein und aus)