Accio – SwiftPM für iOS

13. August 2019

Eine Geschichte der Hoffnung

Auf der WWDC 2019 ist der lang ersehnte SwiftPM-Support in Xcode erschienen. Doch die Apple-Entwicklergemeinde hoffte schon seit der Ankündigung des Swift Package Managers im November 2015 darauf, dass Xcode in Zukunft das automatische Abrufen und Integrieren von Open-Source-Frameworks unterstützt. Doch bis vor wenigen Monaten tat sich nichts. Da CocoaPods und Carthage bereits als Dependency Manager für diese Plattformen verfügbar waren, schien es keinen Druck zu geben, iOS-Projekte gezielt anzugehen.

Das bedeutete jedoch nicht, dass SwiftPM dieses Ziel aufgab - und auch die Community hat niemals aufgehört, es zu versuchen. Es gab mehrere Ansätze, SwiftPM für die iOS-Entwicklung einzusetzen, darunter auch diesen aktuellen Leitfaden zu GitHub. Aber alle waren ziemlich umständlich und fehleranfällig. Der angekündigte Support von Apple wird vermutlich auch viele fehlende Features wie SE-2866 und SE-3948 implementieren, aber bis es soweit ist, müssen noch andere Lösungen gesucht werden.

Vergleich mit dem Status Quo

Wie bereits erwähnt, sind CocoaPods und Carthage zwei etablierte Dependency Manager, zwischen denen sich die Apple Entwicklergemeinschaft entscheiden kann. Worin lägen also die Vorteile gegenüber CocoaPods und Carthage, wenn SwiftPM demnächst das Erstellen für iOS & Co. unterstützt und sogar in Xcode integriert ist? Oder um es anders auszudrücken: Welche Nachteile haben CocoaPods und Carthage, die verbessert werden könnten? Lasst uns einen kurzen Vergleich anstellen:

CocoaPods ist in Ruby geschrieben und wurde erstmals 2011 entwickelt. Es verwendet zwei verschiedene Manifest-Formate namens "Podspec" (für Framework-Autoren) und "Podfile" (für App-Entwickler), die von den beiden beliebtesten Dependency-Managern in der Ruby-Community inspiriert sind. Neben der Möglichkeit, über Git-Repo-URLs auf Abhängigkeiten zu verweisen, bietet es auch eine begleitende Website CocoaPods.org, auf der Framework-Entwickler offizielle Releases erstellen und App-Entwickler über die Suche Frameworks finden können. Daher kann CocoaPods als zentralisierter Depencency Manager betrachtet werden.

Damit ein Framework CocoaPods unterstützen kann, muss es eine "Podspec"-Datei mit Informationen über seine Hauptentwickler, seine Lizenz, die Projektstruktur und die unterstützten Plattformversionen bereitstellen. App-Entwickler benötigen eine " Podfile ", in der die Dependencies spezifiziert werden können. Dazu gehören ihre Versionsanforderungen und die Art und Weise, wie sie integriert werden sollen, alles in einfachem Ruby geschrieben. CocoaPods unterstützt sowohl die Integration per Code als auch über dynamische Frameworks. Um alles richtig zu verlinken, erstellt CocoaPods einen Xcode-Workspace und verwaltet diesen automatisch. Entwickler müssen dann diese neue Datei anstelle der normalen Xcode-Projektdatei verwenden.

Carthage ist in Swift geschrieben und die Entwicklung begann bereits 2014, kurz nach der Veröffentlichung von Swift 1.0. Sein Manifest ist ein benutzerdefiniertes OGDL-konformes Format namens "Cartfile". Es wird sowohl von Framework-Autoren als auch von App-Entwicklern verwendet. Eine zweite Datei mit dem Suffix ".private" kann von Framework-Autoren für Abhängigkeiten verwendet werden, die nur für Entwicklungs- und Testzwecke benötigt werden. Die Cartfile wird jedoch nicht für Frameworks benötigt, solange sie keine Subdependencies aufweisen. Gemeinsame Xcode-Schemata werden verwendet, um festzustellen, was gebaut werden soll. Carthage hat keine zugehörige Website, alle Abhängigkeiten werden über Git Repo-URLs (oder lokale Pfade) verknüpft, so dass es als dezentraler Dependency Manager betrachtet werden kann.

Das Cartfile-Format ist im Vergleich zu CocoaPods sehr restriktiv, da es keine Informationen über seine Hauptentwickler, seine Lizenz, die Projektstruktur oder die unterstützten Plattformversionen enthält. Es handelt sich im Grunde nur um eine Liste von Abhängigkeitspfaden mit Versionsanforderungen. Carthage macht auch keine automatische Verknüpfung. Benutzer müssen die von Carthage erstellten dynamischen Frameworks manuell in ihren Projekten verlinken. Darüber hinaus ist eine zusätzliche Erstkonfiguration innerhalb von Xcode erforderlich, um Probleme bei der Bereitstellung im App Store zu vermeiden.

Beide, sowohl CocoaPods als auch Carthage können so konfiguriert werden, dass sie nur die Frameworks erstellen, die für die erforderliche(n) Plattform(en) benötigt werden. Beide unterstützen das Herunterladen von vorkonfigurierten Binärdateien, wenn sie von Framework-Autoren bereitgestellt wurden. Zudem können beide auch so konfiguriert werden, dass sie zwischengespeicherte Builds von Frameworks innerhalb eines Projekts wiederverwenden.

Carthages Vorteile gegenüber CocoaPods liegen darin, dass es unaufdringlich ist, indem es nur xcodebuild-Befehle ausführt und in Swift geschrieben wird, so dass die Community leichter Verbesserungen vornehmen kann. (Dazu muss man jedoch wissen, dass es mit ReactiveSwift geschrieben ist, was viele Entwickler abschrecken könnte). Ein weiterer Vorteil für Framework-Autoren ist, dass es einfacher zu unterstützen ist - jedoch ist ein Xcode-Projekt mit einem gemeinsamen Schema erforderlich.

Die Vorteile von CocoaPods gegenüber Carthage sind ebenso vielfältig: Die automatisierte Integration erspart Entwicklern viele manuelle Schritte. Zudem ist CocoaPods besser auffindbar und unterstützt die Modularisierung eines Frameworks in mehrere wählbare Module. Carthage hingegen löst solche Situationen, indem es immer alle verfügbaren Module baut, unabhängig davon, welche tatsächlich benötigt werden. Das führt zu unnötigen Wartezeiten.

SwiftPM sollte meiner Meinung nach in Zukunft alle eben genannten Vorteile vereinen und sogar völlig neue Funktionen bieten:

Sowohl CocoaPods als auch Carthage verfügen nicht über integrierte Mechanismen zur Wiederverwendung von gecachten Builds von Frameworks über mehrere Projekte hinweg, was zu unnötigen Buildzeiten führt. Beide sind nicht in klassischem, objektorientiertem Swift geschrieben, wenn man von einigen funktionalen Ergänzungen absieht, die fast für die gesamte Community zugänglich sind. Auch haben beide ihre eigenen Manifestformate, die weder in Swift geschrieben sind, noch von der Swift-Community diskutiert oder strukturell überprüft werden.

Ein praktischer Ansatz zur schnellen Umsetzung

Wir alle wissen, dass SwiftPM eine Weile brauchen wird, um all diese Funktionen offiziell zu unterstützen. Aber vielleicht gibt es eine Möglichkeit, das, was SwiftPM heute schon bietet, zu nutzen und die Lücken mit Alternativen zu schließen, die es heute schon gibt? Wie würde das aussehen? Wäre es besser, als nur CocoaPods oder Carthage zu verwenden?

Mal sehen, was SwiftPM derzeit bietet:

  • SwiftPM hat bereits ein relativ flexibles Manifest-Format, das viel flexibler ist als das Carthages-Format. Noch nicht perfekt, aber es unterstützt bereits Modularität und minimale Plattformanforderungen, um nur zwei Beispiele zu nennen.
  • SwiftPM hat die Auflösung von Abhängigkeiten bereits implementiert. Außerdem hat es einen Unterbefehl show-dependencies, der so konfiguriert werden kann, dass er JSON als Ausgabeformat verwendet, damit wir den erstellten Abhängigkeitsgraphen ausdrucken können.
  • SwiftPM kann auch ein Xcode-Projekt mit einem gemeinsamen Schema mit einem Framework-Ziel erstellen, das durch kleine Änderungen leicht auf iOS-, tvOS-, macOS- oder watchOS-Plattformen ausgerichtet werden kann.

Welche Hauptmerkmale fehlen im Augenblick noch? Und könnten wir das beheben? Schauen wir mal:

  • SwiftPM kann kein Produkt entwickeln, das z. B. direkt in iOS-Projekten verwendet werden kann. Lösungsansatz: Wir könnten xcodebuild mit einem generierten Xcode-Projekt verwenden.
  • SwiftPM unterstützt nicht die Einbeziehung von Ressourcen wie z. B. Bilder mit Zielen. Lösungsansatz: Wir verwenden bestehende Xcode-Projekte, die bereits in Frameworks mit Carthageunterstützung bereitgestellt wurden, wieder.

Nun, da wir die offensichtlichsten Mängel erkannt haben, ist es da nicht die naheliegendste Lösung, es einfach zu halten und Carthage ausschließlich zum Builden zu nutzen? Carthages build-Befehl führt xcodebuild bereits für uns aus, so dass er beide Probleme gleichzeitig beheben würde. Aber würden wir alle Nachteile von Carthage umgehen, indem wir SwiftPM für die Auflösung von Abhängigkeiten und für das Manifest-Format verwenden?

Lasst uns die Nachteile von Carthage prüfen:

  • Automatisierte Xcode-Integration: Diese würde leider immer noch fehlen.
  • Framework-Erkennung: SwiftPM listet dies in seinen Entwicklungsideen auf, vermerkt aber zutreffend: "Das wird ein großes Projekt." - Also, hier auch ein "Nein".
  • Modularisierungsunterstützung: Gelöst! SwiftPM unterstützt die Modularisierung.

Wir sehen also, dass mindestens ein Nachteil behoben werden kann. Die Framework-Erkennungsfunktion ist eine nette Sache, kann aber zu einem späteren Zeitpunkt in Betracht gezogen werden. Auch sollte das vermutlich in einer Weise geschehen, die von der Gemeinschaft diskutiert und akzeptiert wird. Der eigentliche Mangel, der hier behoben werden muss, ist die automatisierte Xcode-Integration. Wenn wir also diesen pragmatischen Ansatz verfolgen, indem wir SwiftPM für die Auflösung von Abhängigkeiten und Carthage für den Aufbau der gelösten und ausgecheckten Frameworks verwenden, müssen wir nur noch die automatische Xcode-Integration selbst hinzufügen, um SwiftPM bereits heute zu verwenden.... das klingt machbar!

Ein neuer Dependency Manager ist geboren

Ich möchte euch Accio (sprich: "AH-ki-oh") vorstellen, einen neuen SwiftPM-basierten Dependency Manager, der im Grunde genau das tut, was oben beschrieben wurde, und zusätzlich einige Funktionen bietet, die weder Carthage noch CocoaPods haben. Bevor ich jedoch näher auf diese Funktionen eingehe, lasst uns zuerst einen Blick darauf werfen, wie man Accio benutzt.

Zuerst müssen wir es mit diesen beiden Befehlen installieren:

    brew tap JamitLabs/Accio https://github.com/JamitLabs/Accio.git
    brew install accio

Beachtet, dass sowohl Xcode 10.2 Kommandozeilen-Tools (sie beinhalten SwiftPM) als auch Carthage installiert sein müssen, damit Accio funktioniert, also installiert sie gegebenenfalls.

Als nächstes erstellen wir ein neues iOS-Projekt, z.B. mit der "Single View App":

iOS-Projekt erstellen

Das Ergebnis ist ein einfaches, buildbares iOS-App-Projekt, das so strukturiert ist:

buildbares iOS-App-Projekt

Erstellt das Projekt und führt es aus, um sicherzustellen, dass es ordnungsgemäß erstellt wurde und ohne Abstürze läuft. Nachdem wir das von unserer Checkliste abgehakt haben, fügen wir einige Abhängigkeiten hinzu. Um das zu tun, benötigen wir eine Package.swift Manifestdatei - erstellen wir sie!

Accio stellt einen init-Befehl zur Verfügung, der für uns eine Package.swift-Datei erstellt, die mit einer elementaren Grundstruktur ausgestattet ist. Um sie nutzen zu können, müssen wir den Namen unserer Xcode-Projektdatei und den Namen unseres Apps-Targets angeben. In diesem Beispiel sind beide Demo, so dass wir die Manifestdatei wie folgt initialisieren können:

    accio init --project-name "Demo" --target-name "Demo"

Beachtet jedoch, dass das Argument --target-name auch mehrere Ziele akzeptiert. Wir müssen sie nur mit einem , trennen, also fügen wir auch unser Unit-Test-Ziel hinzu:

    accio init --project-name "Demo" --target-name "Demo,DemoTests"

Das Ergebnis ist eine Package.swift-Datei mit folgendem Inhalt:

    // swift-tools-version:5.0
    import PackageDescription

    let package = Package(
        name: "Demo",
        products: [],
        dependencies: [
            // add your dependencies here, for example:
            // .package(url: "https://github.com/User/Project.git", .upToNextMajor(from: "1.0.0")),
        ],
        targets: [
            .target(
                name: "Demo",
                dependencies: [
                    // add your dependencies scheme names here, for example:
                    // "Project",
                ],
                path: "Demo"
            ),
            .testTarget(
                name: "DemoTests",
                dependencies: [
                    // add your dependencies scheme names here, for example:
                    // "Project",
                ],
                path: "DemoTests"
            ),
        ]
    )

Beachtet, dass Accio automatisch erraten hat, dass DemoTests wahrscheinlich ein .testTarget und Demo ein plain.target ist. Außerdem hat Accio die folgenden zwei Zeilen in die .gitignore Datei eingefügt, um zu verhindern, dass temporäre Dateien (unter.accio) und Produkte (unter Dependencies) übertragen werden:

    Dependencies/
    .accio/

In einem realen Projekt können die für die path-Parameter eingestellten Werte falsch sein, also überprüft sie unbedingt! Für dieses Demo-Beispiel sind die Standardeinstellungen geeignet.

Als nächstes fügen wir unsere ersten Abhängigkeiten hinzu. Um zu zeigen, welche Art von Bibliotheken Accio unterstützt, versuchen wir das beliebteste Swift-Framework (Alamofire) und das beliebteste Obj-C-Framework (MBProgressHUD). Da beide keine Abhängigkeiten haben, fügen wir außerdem Moya zum Spiel hinzu, vor allem, wenn man bedenkt, dass es drei verschiedene Varianten gibt (Moya, RxMoya und ReactiveMoya):

    // swift-tools-version:5.0
    import PackageDescription

    let package = Package(
        name: "Demo",
        products: [],
        dependencies: [
            // Elegant HTTP Networking in Swift
            .package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "4.8.2")),

            // MBProgressHUD + Customizations
            .package(url: "https://github.com/jdg/MBProgressHUD.git", .upToNextMajor(from: "1.1.0")),

            // Network abstraction layer written in Swift.
            .package(url: "https://github.com/Moya/Moya.git", .upToNextMajor(from: "13.0.0"))
        ],
        targets: [
            .target(
                name: "Demo",
                dependencies: [
                    "Alamofire",
                    "MBProgressHUD",
                    "Moya",
                ],
                path: "Demo"
            ),
            .testTarget(
                name: "DemoTests",
                dependencies: [
                    // add your dependencies scheme names here, for example:
                    // "Project",
                ],
                path: "DemoTests"
            ),
        ]
    )

Beachtet, dass wir für jede Abhängigkeit sowohl die Quelle der Abhängigkeiten in die Parameterliste der Package-Typen-Dependencies(Zeilen 8-15) als auch die Namen der Bibliotheken, die wir mit den target dependenciesverlinken möchten (Zeilen 21-23), hinzufügen mussten. Für dieses Beispiel habe ich mich für die einfache Moya-Variante entschieden.

Nachdem wir nun unsere Abhängigkeiten spezifiziert haben, installieren wir sie mit Accio.

Installieren von Abhängigkeiten mit Accio

Accio stellt zwei separate Befehle für die Installation zur Verfügung: install und update. Der Befehl update prüft immer, ob neuere Versionen von Abhängigkeiten innerhalb der angegebenen Bereiche verfügbar sind und aktualisiert sie bei Bedarf. Der install-Befehl hingegen aktualisiert sich nur, wenn es noch keine Package.resolved-Datei gibt, was bedeutet, dass keiner der beiden Befehle jemals zuvor erfolgreich ausgeführt wurde. Da wir noch nie Abhängigkeiten installiert haben, werden beide Befehle dasselbe tun.

Aber bevor wir irgendwelche Installationsbefehle ausführen, sollten wir unser Projekt schließen, wenn es in Xcode geöffnet ist. Dies wird alle Probleme mit der automatischen Xcode-Integration von Accio vermeiden, wie ihr euch vielleicht erinnert. Okay? Jetzt einfach laufen lassen:

    accio update

Zu dem Zeitpunkt an dem ich das schreibe, führt dies zu einem Fehler wie diesem (Kurzfassung):

    ✨ Updating dependencies ...
    Fetching [...]
    [https://github.com/jdg/MBProgressHUD.git](https://github.com/jdg/MBProgressHUD.git) has no manifest for v1.1.0
    Error: 'swift package update' returned with error code 1.

Wie der Fehler zeigt, hat MBProgressHUD zu diesem Zeitpunkt noch keine Package.swift Manifestdatei. Aber erinnert ihr euch, dass Accio derzeit nur die Manifestdatei zur Auflösung von Abhängigkeiten verwendet? Da wir das Projekt nicht benötigen, um es tatsächlich mit SwiftPM zu erstellen, benötigen wir nur eine richtig konfigurierte Package.swift-Datei. Und das Hinzufügen ist sehr einfach - die README von Accio hat einen ausführlichen Abschnitt mit Anweisungen und Manifestbeispielen für Copy & Paste. Schaut es euch an!

Da MBProgressHUD eines der vielen beliebten Projekte ist, bei denen die Unterstützung für Accio jedoch sogar Teil der Integrationstests ist, gibt es bereits eine Fork- und eine Pull-Anforderung, die bereits zusammengeführt worden sein könnten, während ihr dies lest (was die nächste Änderung unnötig machen würde). Also lasst uns stattdessen die Fork nehmen:

    .package(url: "[https://github.com/AccioSupport/MBProgressHUD.git](https://github.com/AccioSupport/MBProgressHUD.git)", .branch("master"))),

Beachtet, dass wir auch .upToNextMajor zu .branch geändert haben. Für einen vollständigen Überblick über alle verfügbaren Optionen lest diesen Abschnitt (Link nicht mehr verfügbar) in den offiziellen Manifest-Docs auf GitHub.

Nun lasst uns versuchen, die Abhängigkeiten mit diesem Bugfix erneut zu installieren:

    accio update

Eure Ausgabe sollte nun in etwa so aussehen (verkürzt):

    ✨ Updating dependencies ...
    Fetching [https://github.com/Alamofire/Alamofire.git](https://github.com/Alamofire/Alamofire.git) [...]
    ✨ Resolving dependencies for target 'Demo' on platform 'iOS' ...
    *** Building scheme "Alamofire iOS" in Alamofire.xcworkspace
    *** Building scheme "MBProgressHUD" in MBProgressHUD.xcworkspace
    *** Building scheme "Result-iOS" in Result.xcodeproj
    *** Building scheme "Moya" in Moya.xcodeproj
    ✨ Copying build products of target 'Demo' into 'Dependencies' ...
    ✨ Adding frameworks ["Alamo...] to project navigator group 'Dependencies/Demo' & linking with target 'Demo' ...
    ✨ Creating new copy build script phase 'Accio' for 'Demo'...
    ✨ Updating paths in build script 'Accio' for target 'Demo' ...
    ✨ Successfully updated dependencies.

Beachtet, dass Accio automatisch erkannt hat, dass unser Projekt auf die iOS-Plattform abzielt und nur Schemata für diese Plattform erstellt hat, ohne unnötige Schemata wie Alamofire tvOS. Außerdem installierte es korrekterweise notwendige Abhängigkeiten wie Result, übersprang aber automatisch unnötige Schemata wie RxSwift/ReactiveSwift und RxMoya/ReactiveMoya.

Okay, jetzt, da der Output gut aussah, lasst uns unser Projekt öffnen und untersuchen:

iOS-Projekt analysieren

Beachtet, dass Accio automatisch eine neue Gruppe namens Dependencies in den Projektnavigator eingefügt und die angegebenen Frameworks mit dem Demo-Ziel verlinkt hat. Außerdem hat es dem Demo-Ziel ein neues Build-Skript hinzugefügt, um sicherzustellen, dass der sechs Schritte lange Konfigurationsprozess von Carthage durch den Installations-Befehl automatisch durchgeführt wird. Okay, also, was ist der nächste Schritt?

Es gibt keinen! Wenn alle Abhängigkeiten Unterstützung für Accio haben, genügt es, sie in der Manifestdatei anzugeben und den Installations-Befehl auszuführen. Wir haben nun einige Abhängigkeiten erfolgreich in unser iOS-Projekt integriert und konnten sie sofort nutzen - Build and Run funktioniert einfach!

Zum Beispiel könnten wir unsere ViewController.swift Datei auf diesen Code aktualisieren:

    import UIKit
    import MBProgressHUD
    import Moya

    // Let's define an API with Moya
    public enum GitHub: Moya.TargetType {
        case zen

        public var baseURL: URL { return URL(string: "https://api.github.com")! }
        public var path: String { return "/zen" }
        public var method: Moya.Method { return .get }
        public var task: Task { return .requestPlain }
        public var sampleData: Data { return Data() }
        public var headers: [String: String]? { return nil }
    }

    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()

            // create some label with centered text
            let statusCodeLabel = UILabel(frame: self.view.bounds)
            statusCodeLabel.textAlignment = .center
            self.view.addSubview(statusCodeLabel)

            // Let's use Moya and MBProgressHUD (note that Moya uses Alamofire internally)
            let hud = MBProgressHUD.showAdded(to: view, animated: true)
            MoyaProvider<GitHub>().request(.zen) { result in
                hud.hide(animated: true)
                statusCodeLabel.text = "Zen Status: \(result.value!.statusCode)"
            }
        }
    }

Wenn wir jetzt unsere App erstellen und ausführen, sieht das Ergebnis so aus:

Alles funktioniert wie erwartet! 🎉

Als nächstes wollen wir ein Framework nur für Testzwecke hinzufügen, zum Beispiel die flexible SnapshotTesting-Bibliothek von pointfreeco:

    // swift-tools-version:5.0
    import PackageDescription

    let package = Package(
        name: "Demo",
        products: [],
        dependencies: [
            // Alamofire, MBProgressHUD & Moya unchanged ...
            // 📸 Delightful Swift snapshot testing. << added
            .package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", .upToNextMajor(from: "1.5.0")),
        ],
        targets: [
            // Demo target unchanged ...
            .testTarget(
                name: "DemoTests",
                dependencies: [
                    "SnapshotTesting", // << added
                ],
                path: "DemoTests"
            ),
        ]
    )

Dann führen wir einfach accio update erneut aus, warten darauf, dass es abgeschlossen ist, und öffnen das Projekt erneut:

iOS-Projekt mit accio update

Accio hat wieder das Richtige getan: Es hat das Framework nur mit dem Testziel verlinkt und "copy files" anstelle von copy skript verwendet. Wenn ihr noch nie zuvor vom Unterschied zwischen diesen Carthage-Konfigurationsdetails gehört habt, macht euch nichts daraus: Mit Accio braucht ihr sie sowieso nicht mehr zu kennen.

Mehrstufiges Caching zur Minimierung der Buildzeiten

In den obigen Befehlsausgaben wurden mehrere Teile entfernt. Als wir unsere Abhängigkeiten zum ersten Mal installierten, enthielt die Ausgabe tatsächlich die folgenden Zeilen:

    ✨ Saved build products for Alamofire in local cache.
    ✨ Saved build products for MBProgressHUD in local cache.
    ✨ Saved build products for Result in local cache.
    ✨ Saved build products for Moya in local cache.

Ohne jegliche Konfiguration speichert Accio standardmäßig alle erfolgreich erstellten Build-Produkte in einem gerätelokalen Cache, der sich im MacOS-Cache-Ordner befindet:

    /Users/Name/Library/Caches/Accio/Cache

Auf diese Weise - nachdem wir accio update nach dem Hinzufügen von Testabhängigkeiten erneut durchgeführt hatten - enthielten die Ausgaben tatsächlich die folgenden Zeilen.

    ✨ Found cache for Alamofire in local cache - skipping build.
    ✨ Found cache for MBProgressHUD in local cache - skipping build.
    ✨ Found cache for Alamofire in local cache - skipping build.
    ✨ Found cache for Result in local cache - skipping build.
    ✨ Found cache for Moya in local cache - skipping build.
    ✨ Saved build products for SnapshotTesting in local cache.

Das bedeutet, bereits erstellte Frameworks werden wiederverwendet und nicht jedes Mal neu erstellt. Ein Build-Produkt innerhalb des Cache wird durch eine Kombination aus dem Namen der Library, dem Commit-Hash, der Swift-Version und der Zielplattform eindeutig identifiziert.

Zum Beispiel ist der Pfad für gecachte Build-Produkte für Alamofire von oben:

    Swift-5.0/Alamofire/75bba56748359f297a83f620d45f72cf4ebee4e7/iOS.zip

Wenn ein bestimmtes Commit eines Frameworks bereits für ein Projekt erstellt wurde, stellt dieser lokale Cache sicher, dass es für andere Projekte auf dem gleichen Gerät wiederverwendet werden kann. Das allein reduziert die Kompilierzeit bereits erheblich, wenn ihr in mehreren Projekten arbeitet.

Aber Accio geht noch einen Schritt weiter: Es bietet auch einen optionalen geteilten Cache! Durch einfaches Hinzufügen der Option --shared-cache-path (oder der Abkürzung -c) könnt ihr einen benutzerdefinierten Pfad angeben, der für euer gesamtes Team freigegeben werden kann:

    accio update -c '/Volumes/GoogleDrive/Team Share/AccioSharedCache'

Indem ihr einen Cloud-Dienst wie Google Drive verwendet und einen Ordner für eure freigegebenen Cache-Dateien erstellt, könnt ihr die Buildzeiten der Dependencies des gesamten Teams drastisch reduzieren. Nur die erste Person, die einen bestimmten Commit oder eine bestimmte Version eines Frameworks erstellt, muss warten, alle Folgenden werden einfach in den Cache gelangen!

Es gibt sogar eine Option, um standardmäßig einen gemeinsamen Cache zu verwenden. Lasst das Folgende nur einmal laufen:

    accio set-shared-cache '/absolute/path/to/to/your/shared cache'

Dadurch wird eine config.json-Datei unter Application Support/Accio mit dem von euch gewählten Pfad erstellt. Dieser Pfad ist benutzerspezifisch, so dass andere Benutzer auf demselben Computer nicht betroffen sind. Um es rückgängig zu machen, entfernt man einfach die Datei config.json. Um den Pfad für den gemeinsamen Cache zu ändern, führt man den Befehl einfach mit dem neuen Pfad erneut aus. Einen lokalen und einen gemeinsamen Cache zu haben, ist eine große Verbesserung gegenüber Carthage & CocoaPods!

Fazit

Mit dem neuen Dependency Manager Accio könnt ihr bereits heute von SwiftPM profitieren und es problemlos für iOS & Co. einsetzen. Es verwendet Carthage im Moment zum Builden, daher ist es sehr einfach, Unterstützung für Accio hinzuzufügen. Aber es vermeidet die Nachteile von Carthage. Es behebt sie nicht nur, sondern bietet sogar das beste verfügbare Caching-System - so könnt ihr und euer Team euch von wahrscheinlich etwa 90% der Aufbauzeiten für Abhängigkeiten verabschieden! 🍻

Dieser Artikel ist eine aktualisierte Übersetzung des ursprünglichen Artikels Accio – SwiftPM for iOS & Co. today! von Cihat Gündüz, Head of iOS Development bei Jamit Labs und Hauptautor von Accio.