Ich experimentiere in meiner Library Kiri gerade mit einem neuen Ansatz für Netzwerk-Requests. Kiri ist eine von Moya inspirierte abstrakte Networking-Schicht. Die Architektur von Moya hat mich sehr beeindruckt, und um möglichst viel daraus zu lernen, habe ich beschlossen, dieselbe Architektur selbst nachzubauen, statt sie per Copy-and-paste zu übernehmen. :D

Eine Client-Library lässt sich in drei Teile zerlegen.

Request

Normalerweise muss man den Netzwerk-Request jedes Mal neu zusammenbauen. In Kiri dagegen wird die gesamte Netzwerkkonfiguration an einer Stelle gebündelt, etwa in einer Klasse oder einem Enum. Diese Klasse bzw. dieses Enum muss dem Protokoll RequestContainer entsprechen.

Beispielcode sieht so aus.

enum TestAPIRequest {
  case getitems
  case postitem
  case updateitem
  case deleteitem
}

enum TestAPIRequest: RequestContainer {
  var baseURL: URL {
    return URL(string: "https://www.example.com/api/v1")!
  }
  
  var path: String {
    switch self {
      case .getitems:
      return "/getitems"
      .....
    }
  }
  
  ....
}

Für das Design eines API-Clients ist das die bessere Lösung. Bevor man mit dem Client-Modul loslegt, kann man die gesamte Struktur auf einen Blick erfassen. Außerdem muss man nicht jeder Klasse konstante Strings übergeben — Kiri kann gemeinsame Teile wie baseURL zentral halten, sodass man beim Herausziehen solcher Dinge in eigene Methoden doppelten Code in der App vermeidet.

Wenn deine API allerdings pro Endpoint unterschiedlich aufgebaut ist oder Ähnliches, wird es weniger einfach. In so einem Fall will man vielleicht pro API eine eigene Klasse anlegen — auch das ergibt Sinn. Wer (leider) auf so eine API trifft, sollte sich stattdessen APIKit ansehen. Es ist von vornherein auf separate Klassen pro API ausgelegt und verfolgt bei der Response einen generischeren Ansatz als Kiri oder Moya.

Networking

iOS bringt mit URLSession eine eigene Networking-Klasse mit. Aber es ergibt heute keinen Sinn mehr, URLSession selbst zu wrappen. Wir haben Alamofire. Alamofire validiert Request und Response automatisch und liefert auch JSON zurück. Ich bevorzuge es allerdings, die Response als Data zu behalten, denn eine Netzwerk-Library sollte mehrere Response-Formate unterstützen.

Kiri nutzt Alamofire für Netzwerk-Requests und delegiert den Aufbau des Requests an Alamofire.

In den meisten Fällen funktioniert Alamofire hervorragend. Wenn man aber primitiveres Networking braucht, etwa um Parameter komplett selbst zusammenzubauen, ist es vielleicht nicht die richtige Wahl.

Response

Ich habe festgestellt, dass es hier einen Trade-off gibt: Macht man die Response generisch, zersplittert der Request in separate Klassen. Macht man umgekehrt den Request generisch, zersplittert die Response.

Ich habe den Code von Moya und APIKit gelesen. Meiner Meinung nach sind das die ausgereiftesten Architekturen im Swift-Networking — beide mit ihren Vor- und Nachteilen.

Die perfekte Lösung fürs Networking habe ich noch nicht gefunden. Im Moment ist die beste Antwort wohl, je nach Anforderungen der eigenen App klug zu wählen.

Referenzen

Kiri ist deutlich kleiner als Moya, funktioniert in meinem persönlichen Projekt aber einwandfrei. Probier es doch einfach mal aus!