Integration von AFNetworking, ReactiveCocoa und Kiwi

CocoaPods: einfaches Dependency Management in iOS
Kommentare

In der Softwareentwicklung ist es wie im wahren Leben. Nicht immer will das Rad neu erfunden werden, sei es aus Mangel an Zeit oder weil ein Problem anderweitig bereits besser gelöst wurde, als man selbst je im Stande dazu gewesen wäre. Die Popularität von iOS und das bereits „fortgeschrittene“ Alter der Plattform haben so manches nützliche „Rad“ über die Zeit hervor gebracht. Hierbei seien exemplarisch AFNetworking, ReactiveCocoa oder auch Kiwi genannt. Die Frage ist, wie diese „Räder“ in ein Xcode-Projekt kommen. Die Antwort darauf lautet: mit CocoaPods.

Das Einbinden externer Libs oder Frameworks gestaltet sich dabei immer nach dem gleichen Muster. Quelldateien runterladen, diese im Projekt ablegen und entsprechend HEADER_SEARCH_PATH und/oder FRAMEWORK_SEARCH_PATH anpassen. Doch leider ist das nicht die ganze Wahrheit. Denn je nach Framework hat dieses möglicherweise Abhängigkeiten zu weiteren Frameworks, welche dann ihrerseits wiederum Abhängigkeiten mit sich bringen können. Hinzu kommt, dass ein Framework Abhängigkeiten mitunter nur in bestimmten Versionen besitzt, beispielsweise ReactiveCocoa in Version 1.9.5 ist abhängig von JRSwizzle 1.0 und libextobjc 0.2.5. Mit einem Mix aus ARC- und Non-ARC-fähigem Code lässt sich die Komplexität eines Projekts nochmals steigern. Wer dann noch beispielsweise Kiwi nur dann ins Projekt kompiliert und gelinkt haben möchte, wenn das Unit Test Target ausgeführt wird, ist am Ende eines Arbeitstages froh, wenn alles endlich läuft. Wer sich bis dato während eines iOS-Projekts immer wieder mit dieser Art von Herausforderung konfrontiert sah, findet mit CocoaPods den richtigen Helfer.

Installation von CocoaPods

Bei CocoaPods handelt es sich um ein ruby gem, das vor der eigentlichen Verwendung einmalig installiert werden muss. Mittels $ [sudo] gem install cocoapods werden alle notwendigen ruby gems für die Verwendung von CocoaPods geladen und installiert (aktuell in Version 0.24.0). Nach erfolgreicher Installation wird durch den Aufruf von $ pod setup dieses initialisiert und somit sind alle Voraussetzungen zum Einsatz von CocoaPods, sei es in einem neuen oder bestehenden Projekt, erfüllt. Die eigentliche Deklaration der Abhängigkeiten zu weiteren Frameworks, die in dem Zusammenhang auch Pods genannt werden, erfolgt in einer Datei mit dem Namen Podfile. Diese befindet sich auf der gleichen Dateiebene wie das Xcode-Projektverzeichnis selbst:

platform :ios, '7.0'

Das erste Podfile

Da CocoaPods sowohl für iOS als auch OS-X-Projekte zum Einsatz kommen kann, beginnt jedes Podfile mit der Definition der Plattform, in unserem Falle iOS, gefolgt von dem Deployment Target, hier 7.0. Ein Aufruf des Kommandos $ pod auf der Dateiebene des Podfiles startet CocoaPods und nach kurzer Zeit sollte ein Hinweis in der Konsole erscheinen, dass ab jetzt das Projekt nur noch durch die Datei mit der Endung .xcworkspace geöffnet werden darf und nicht wie bisher über die ursprünglich durch Xcode erzeugte .xcodeproj-Datei. Denn CocoaPods hat neben einem Xcode Workspace ein neues Xcode-Projekt in Pods/Pods.xcodeproj angelegt, welches Teil des neuen Workspace-Projekts ist. In diesem Projekt mit dem Namen Pods befinden sich ab sofort alle von CocoaPods verwalteten Frameworks. Das ursprüngliche Xcode-Projekt wurde zeitgleich angepasst und ist Teil des neuen Workspace-Projekts, sodass HEADER_SEARCH_PATH nun auf ${PODS_ROOT}/Headers verweist. Weiterhin wird die statische Bibliothek libPods.a, die als Produkt aus dem Pods-Projekt resultiert, unter TARGETS | BUILD PHASES | LINK BINARY WITH LIBRARIES eingetragen. Alle Projektanpassungen erfolgen dabei über die automatisch erstellte Pods.xcconfig-Datei, wodurch der Eingriff durch CocoaPods in das ursprüngliche Projekt sehr gering gehalten wird. Bis dato ist das Projekt noch sehr minimal, da noch keine weiteren Abhängigkeiten definiert wurden. Bevor ein Pod dem Podfile hinzugefügt werden kann, stellt sich die Frage, welche Pods es gibt und welche die aktuellste Version ist. Hierzu bietet die Webseite von CocoaPods eine Suche aller aktuell verfügbaren Pods inkl. – falls vorhanden – einem Verweis auf das Schwesterprojekt CocoaDocs, das die zugehörige Dokumentation auf das Repository des Projekts z. B. bei GitHub bereitstellt. Des Weiteren ist eine Suche per Kommandozeile durch $ pod search möglich, beispielsweise erzeugt die Suche nach $ pod search AFNetworking die in Listing 1 zu sehende Ausgabe.

$ pod search AFNetwork
-> AFNetworking (2.0.0-RC2)
   A delightful iOS and OS X networking framework.
   pod 'AFNetworking', '~> 2.0.0-RC2'
   - Homepage: h ttps://github.com/AFNetworking/AFNetworking
   - Source:   h ttps://github.com/AFNetworking/AFNetworking.git
   - Versions: 2.0.0-RC2, 2.0.0-RC1, 1.3.2, 1.3.1, 1.3.0, 1.2.1, 1.2.0, 1.1.0, 1.0.1, 1.0, 1.0RC3, 1.0RC2, 1.0RC1, 0.10.1, 0.10.0, 0.9.2, 0.9.1, 0.9.0, 0.7.0,
   0.5.1 [master repo]
   - Sub specs:
     - AFNetworking/Core (2.0.0-RC2)
     - AFNetworking/UIKit+AFNetworking (2.0.0-RC2)

Neben allgemeinen Informationen befindet sich auch die direkte Konfiguration für das Podfile mit der neuesten Version des Pods in der Ausgabe. Damit AFNetworking in Zukunft Teil des Projekts wird, muss es dem Podfile hinzugefügt werden:

platform :ios, '7.0'
pod 'AFNetworking', '2.0.0-RC2'

Nachdem ein weiteres Mal $ pod in der Shell ausgeführt wird, werden die deklarierten Abhängigkeiten geladen und dem Pods-Projekt hinzugefügt. Eine minimale Pod-Deklaration besteht dabei aus dem Schlüsselwort pod, gefolgt von dem Namen des Pods, die Versionsangabe ist optional. Wird auf diese verzichtet, wird automatisch die neueste Version verwendet. Dies sollte jedoch mit Bedacht verwendet werden, da es zu Differenzen im Sourcecode innerhalb eines Projektteams führen kann, gerade im Hinblick auf die Verwendung einer dedizierten Build-Instanz.

Neben der Angabe einer expliziten Versionsnummer lassen sich diese auch mittels: >, >=, <, <= und ~>, auf bestimmte Versionsbereiche eingrenzen. Wer unbedingt die „bleeding edge“-Version eines Pods benötigt oder verwenden möchte, kann dies durch :head als Versionsangabe erreichen.

Aufmacherbild: Cocoa pod on a white background. von Shutterstock / Urheberrecht: Valentyn Volkov [ header = Seite 2: Targets im Podfile ]

Targets im Podfile

Ohne dass es extra spezifiziert werden musste, wurde durch CocoaPods ein globales Target namens :default angelegt, das mit dem ersten Xcode Target verbunden ist. Das bedeutet, dass ein weiteres Xcode Target erstmals keine durch CocoaPods verwalteten Abhängigkeiten zugewiesen bekommt und diese somit nicht verwenden kann. Dies ließe sich pragmatisch dadurch lösen, dass die Einstellungen in den BUILD SETTINGS von einem Target auf das andere übertragen werden würde. Dieser Ansatz hat den Nachteil, dass jede im Nachhinein durchgeführte Änderung wieder von Hand nachgezogen werden muss. Eleganter kann dies dadurch gelöst werden, dass durch die Angabe link_with weitere Xcode Targets im Podfile spezifiziert werden, auf die die Deklarationen des globalen Targets :default übertragen werden (Listing 2).

platform :ios, '7.0'
link_with ['cocoapodsTargetVariant1', 'cocoapodsTargetVariant2']
pod 'AFNetworking', '2.0.0-RC2' 

Über das eben genannte link_with lassen sich Pod-Deklarationen auf weitere Xcode Targets replizieren, was jedoch nicht immer passt, bspw. wenn Kiwi als Unit-Test-Framework in einem eigenen Xcode Target zum Einsatz kommen soll. Zu diesem Zweck lassen sich mittels des Schlüsselworts target weitere Pods deklarieren, die dem spezifizierten Xcode Target zusätzlich zu den Pods aus dem :default Target hinzugefügt werden (Listing 3).

platform :ios, '7.0'
link_with ['cocoapodsTargetVariant1', 'cocoapodsTargetVariant2']
pod 'AFNetworking', '2.0.0-RC2' 
target :unitTest do
  pod 'Kiwi', '2.2.1'
end

Dieses Verhalten kann durch die Angabe von :exclusive => true innerhalb der target-Deklaration umgedreht werden. Dadurch enthält das target nur noch die Pods, die direkt innerhalb dessen spezifiziert wurden und erben nicht mehr automatisch die Pods aus dem übergeordneten target, in unserem Falle die des :default.

Lokale Pods

Ein Großteil der Pods wird auf GitHub gehostet und somit kann jeder eigene Änderungen in existierende Pods einfließen lassen bzw. bestehende Fehler beheben. Doch bevor Änderungen der Gemeinschaft zugänglich gemacht werden, sollten diese in der Regel vorher ausgiebig lokal getestet werden. Das direkte Abändern des Quellcodes innerhalb einer durch CocoaPods verwalteten Ressource kann zu ganz kurzen, lokalen Testzwecken durchgeführt werden. Jedoch sollte einem zuvor bewusst sein, dass ein Aufruf von $ pod die zuvor durchgeführten Änderungen direkt wieder zunichtemacht. Doch auch hierfür bietet CocoaPods durch das Schlüsselwort :path eine Abhilfe (Listing 4).

platform :ios, '7.0'
link_with ['cocoapodsTargetVariant1', 'cocoapodsTargetVariant2']
pod 'AFNetworking', :path => '~/AFNetworking'

Der Pfad lässt sich dabei entweder absolut oder relativ in Bezug auf das Podfile angeben. Jedoch ist das an dieser Stelle auch wieder einmal nur die halbe Wahrheit. Nachdem die Pod-Ressource an eine Stelle außerhalb des von CocoaPods verwalteten Verzeichnisses kopiert und das Podfile entsprechend angepasst wurde, bricht der im Anschluss ausgeführte $ pod-Befehl mit einem Fehler ab, da für das Pod AFNetworking die entsprechende .podspec-Datei fehlt. Eine .podspec-Datei enthält neben allgemeinen Pod-Informationen, wie Name und Version, auch Angaben zu weiteren Abhängigkeiten in Form von Systemframeworks und Pods. Die .podspec-Dateien werden von CocoaPods selbst verwaltet und befinden sich unter OS X in ~/.cocoapods. Hier findet man auch die zu AFNetworking zugehörige .podspec-Datei. Sobald diese in das unter :path spezifizierte Verzeichnis kopiert wurde, führt ein erneutes Ausführen des $ pod-Befehls dazu, dass ab jetzt AFNetworking aus den lokalen Quellen gebaut wird.

Eigene Pods und Pods Repository

Wenn die Vorteile aus einer konsequenten Verwendung von CocoaPods erst einmal erkannt wurden, sieht man sich schnell mit der Frage konfrontiert, was mit eventuell bereits existierenden privaten iOS-Frameworks, die bis dato per Copy and Paste oder auch Git Submodules zwischen den iOS-Projekten verwendet wurden, passiert? Die erste Möglichkeit, die sich vor allem am Anfang eignet, ist, existierende private iOS-Frameworks durch das Hinzufügen einer eigenen .podspec-Datei für den Einsatz von CocoaPods vorzubereiten. Dadurch lässt sich ein privates Framework in weiteren iOS-Projekten als Pods-Ressource, wie in Listing 5 demonstriert, verwenden.

platform :ios, '7.0'
link_with ['cocoapodsTargetVariant1', 'cocoapodsTargetVariant2']
pod 'AFNetworking', :path => '~/AFNetworking'

Diese Lösung hat nur einen Haken, sie skaliert nicht. Zum einen muss jeder Entwickler wissen, unter welchem URI das entsprechend private Framework zu finden ist, zum anderen wirkt sich ein Umzug des Repository-Servers auf bestehende iOS-Projekte aus. Die Lösung hierzu heißt: ein eigener Repository-Server, der, nachdem dieser aufgesetzt wurde, per $ pod repo add NAME URL der lokalen CocoaPods-Installation bekannt gemacht wird. Sobald dies geschehen ist, können private Frameworks in Form von Pods per Namen und optional einer Versionsangabe ohne konkreten URI referenziert werden.

Fazit

CocoaPods ermöglicht auf sehr einfache Art und Weise externe Frameworks, die so genannten Pods, in Xcode-Projekte für iOS oder OS X zu integrieren. Abhängigkeiten zu weiteren externen und internen Frameworks, sowie die entsprechenden Compilereinstellungen werden dabei automatisch verwaltet. Somit lassen sich während der Projektlaufzeit sehr schnell Pods in ein Projekt integrieren, neuere Versionen testen oder auch wieder entfernen, ohne dass permanent manuell in die Projekteinstellungen eingegriffen werden muss.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -