I want to understand basic, abstract and correct architectural approach for networking applications in iOS
: Es gibt keinen "besten" oder "richtigsten" Ansatz zum Erstellen einer Anwendungsarchitektur. Es ist ein sehr kreativer Job. Sie sollten immer die einfachste und erweiterbarste Architektur wählen, die für jeden Entwickler, der mit der Arbeit an Ihrem Projekt beginnt, oder für andere Entwickler in Ihrem Team klar ist, aber ich stimme zu, dass es eine "gute" und eine "schlechte" geben kann " die Architektur.
Sie sagten: „ collect the most interesting approaches from experienced iOS developers
Ich denke nicht, dass mein Ansatz am interessantesten oder korrektesten ist, aber ich habe ihn in mehreren Projekten verwendet und bin damit zufrieden. Es ist ein hybrider Ansatz der oben genannten und auch Verbesserungen meiner eigenen Forschungsanstrengungen. Ich interessiere mich für die Probleme beim Aufbau von Ansätzen, die mehrere bekannte Muster und Redewendungen kombinieren. Ich denke, viele von Fowlers Unternehmensmustern können erfolgreich auf mobile Anwendungen angewendet werden. Hier ist eine Liste der interessantesten, die wir zum Erstellen einer iOS-Anwendungsarchitektur anwenden können ( meiner Meinung nach ): Serviceschicht , Arbeitseinheit , Remote-Fassade , Datenübertragungsobjekt ,Gateway erstellen LevelDB basiert, Layer Supertype , Sonderfall , Domänenmodell . Sie sollten eine Modellebene immer korrekt entwerfen und die Persistenz nicht vergessen (dies kann die Leistung Ihrer App erheblich steigern). Sie können Core Data
dies verwenden. Sie sollten jedoch nicht vergessen, dass dies Core Data
kein ORM oder eine Datenbank ist, sondern ein Objektdiagramm-Manager mit Persistenz als gute Option. Daher kann es sehr oft Core Data
zu schwer für Ihre Anforderungen sein und Sie können sich neue Lösungen wie Realm und Couchbase Lite ansehen oder eine eigene kompakte Objektzuordnungs- / Persistenzschicht erstellen, die auf SQLite oder SQLite basiert . Ich rate Ihnen auch, sich mit dem vertraut zu machenDomain Driven Design und CQRS .
Zuerst denke ich, wir sollten eine weitere Ebene für das Networking schaffen, weil wir keine Fat Controller oder schweren, überforderten Modelle wollen. Ich glaube nicht an diese fat model, skinny controller
Dinge. Aber ich glaube an skinny everything
Ansatz, weil keine Klasse jemals fett sein sollte. Alle Netzwerke können im Allgemeinen als Geschäftslogik abstrahiert werden, daher sollten wir eine andere Ebene haben, auf der wir sie platzieren können. Service Layer ist das, was wir brauchen:
It encapsulates the application's business logic, controlling transactions
and coordinating responses in the implementation of its operations.
In unserem MVC
Bereich Service Layer
ist so etwas wie ein Vermittler zwischen Domänenmodell und Controllern. Es gibt eine ziemlich ähnliche Variante dieses Ansatzes namens MVCS, bei der a Store
tatsächlich unsere Service
Schicht ist. Store
verkauft Modellinstanzen und kümmert sich um das Netzwerk, das Caching usw. Ich möchte erwähnen, dass Sie nicht Ihre gesamte Netzwerk- und Geschäftslogik in Ihre Serviceschicht schreiben sollten . Dies kann auch als schlechtes Design angesehen werden. Weitere Informationen finden Sie in den Domain-Modellen Anemic und Rich . Einige Servicemethoden und Geschäftslogiken können im Modell behandelt werden, sodass es sich um ein "reichhaltiges" Modell (mit Verhalten) handelt.
Ich benutze immer ausgiebig zwei Bibliotheken: AFNetworking 2.0 und ReactiveCocoa . Ich denke, es ist ein Muss für jede moderne Anwendung, die mit dem Netzwerk und den Webdiensten interagiert oder komplexe UI-Logik enthält.
DIE ARCHITEKTUR
Zuerst erstelle ich eine allgemeine APIClient
Klasse, die eine Unterklasse von AFHTTPSessionManager ist . Dies ist ein Arbeitspferd aller Netzwerke in der Anwendung: Alle Serviceklassen delegieren tatsächliche REST-Anforderungen an diese. Es enthält alle Anpassungen des HTTP-Clients, die ich in der jeweiligen Anwendung benötige: SSL-Pinning, Fehlerverarbeitung und Erstellen einfacher NSError
Objekte mit detaillierten Fehlergründen und Beschreibungen aller API
und Verbindungsfehler (in diesem Fall kann der Controller die richtigen Meldungen für anzeigen Benutzer), Festlegen von Anforderungs- und Antwortserialisierern, http-Headern und anderen netzwerkbezogenen Dingen. Dann logisch teile ich die alle API - Anfragen in Subservices oder, richtiger gesagt , Microservices : UserSerivces
, CommonServices
, SecurityServices
,FriendsServices
und so weiter, entsprechend der von ihnen implementierten Geschäftslogik. Jeder dieser Mikrodienste ist eine separate Klasse. Sie bilden zusammen eine Service Layer
. Diese Klassen enthalten Methoden für jede API-Anforderung, verarbeiten Domänenmodelle und geben immer eine RACSignal
mit dem analysierten Antwortmodell oder NSError
an den Aufrufer zurück.
Ich möchte erwähnen, dass Sie, wenn Sie eine komplexe Modell-Serialisierungslogik haben, eine weitere Ebene dafür erstellen: etwas wie Data Mapper, aber allgemeiner, z. B. JSON / XML -> Model Mapper. Wenn Sie über einen Cache verfügen, erstellen Sie ihn auch als separaten Layer / Service (Sie sollten Geschäftslogik nicht mit Caching mischen). Warum? Weil die richtige Caching-Ebene mit ihren eigenen Fallstricken sehr komplex sein kann. Menschen implementieren komplexe Logik, um gültiges, vorhersehbares Caching zu erhalten, wie z. B. monoidales Caching mit Projektionen, die auf Profunktoren basieren. Sie können über diese schöne Bibliothek namens Carlos lesen , um mehr zu verstehen. Und vergessen Sie nicht, dass Core Data Ihnen bei allen Caching-Problemen wirklich helfen kann und es Ihnen ermöglicht, weniger Logik zu schreiben. Wenn Sie eine Logik zwischen NSManagedObjectContext
und Serveranforderungsmodellen haben, können Sie diese auch verwenden Repository habenMuster, das die Logik, die die Daten abruft und sie dem Entitätsmodell zuordnet, von der Geschäftslogik trennt, die auf das Modell einwirkt. Daher empfehle ich, das Repository-Muster auch dann zu verwenden, wenn Sie über eine auf Core Data basierende Architektur verfügen. Repository kann abstrakte Dinge, wie NSFetchRequest
, NSEntityDescription
, NSPredicate
und so weiter in einfachen Methoden wie get
oderput
.
Nach all diesen Aktionen in der Service-Schicht kann der Aufrufer (View Controller) einige komplexe asynchrone Aufgaben mit der Antwort ausführen: Signalmanipulationen, Verkettung, Zuordnung usw. mithilfe von ReactiveCocoa
Grundelementen oder sie einfach abonnieren und Ergebnisse in der Ansicht anzeigen . Ich spritze mit der Dependency Injection in allen diesen Serviceklassen meinen APIClient
, die einen bestimmten Service - Aufruf in entsprechenden übersetzen werden GET
, POST
, PUT
, DELETE
etc. Anfrage an den REST - Endpunkt. In diesem Fall APIClient
wird implizit an alle Controller übergeben, Sie können dies mit einer über APIClient
Serviceklassen parametrisierten explizit machen . Dies kann sinnvoll sein, wenn Sie verschiedene Anpassungen des verwenden möchtenAPIClient
für bestimmte Serviceklassen, aber wenn Sie aus bestimmten Gründen keine zusätzlichen Kopien wünschen oder sicher sind, dass Sie immer eine bestimmte Instanz (ohne Anpassungen) von APIClient
- verwenden, machen Sie es zu einem Singleton, aber NICHT, bitte NICHT Machen Sie keine Serviceklassen als Singletons.
Dann injiziert jeder View Controller erneut mit dem DI die benötigte Serviceklasse, ruft geeignete Servicemethoden auf und erstellt deren Ergebnisse mit der UI-Logik. Für die Abhängigkeitsinjektion verwende ich gerne BloodMagic oder ein leistungsfähigeres Framework Typhoon . Ich benutze niemals Singletons, Gottesunterricht APIManagerWhatever
oder andere falsche Sachen. Denn wenn Sie Ihre Klasse anrufen WhateverManager
, bedeutet dies, dass Sie den Zweck nicht kennen und es sich um eine schlechte Designentscheidung handelt . Singletons sind auch ein Anti-Muster und in den meisten Fällen (außer in seltenen Fällen) eine falsche Lösung. Singleton sollte nur berücksichtigt werden, wenn alle drei der folgenden Kriterien erfüllt sind:
- Das Eigentum an der einzelnen Instanz kann nicht angemessen zugewiesen werden.
- Eine verzögerte Initialisierung ist wünschenswert.
- Ein globaler Zugriff ist nicht anders vorgesehen.
In unserem Fall ist der Besitz der einzelnen Instanz kein Problem, und wir benötigen auch keinen globalen Zugriff, nachdem wir unseren God-Manager in Dienste unterteilt haben, da jetzt nur ein oder mehrere dedizierte Controller einen bestimmten Dienst benötigen (z. B. UserProfile
Controller-Anforderungen UserServices
usw.). .
Wir sollten S
bei SOLID immer das Prinzip respektieren und die Trennung von Bedenken verwenden. Ordnen Sie daher nicht alle Ihre Servicemethoden und Netzwerkaufrufe einer Klasse zu, da dies verrückt ist, insbesondere wenn Sie eine große Unternehmensanwendung entwickeln. Aus diesem Grund sollten wir den Ansatz der Abhängigkeitsinjektion und der Dienste in Betracht ziehen. Ich betrachte diesen Ansatz als modern und post-OO . In diesem Fall teilen wir unsere Anwendung in zwei Teile: Steuerlogik (Steuerungen und Ereignisse) und Parameter.
Eine Art von Parametern wären gewöhnliche "Daten" -Parameter. Das ist es, was wir Funktionen weitergeben, manipulieren, modifizieren, beibehalten usw. Dies sind Entitäten, Aggregate, Sammlungen, Fallklassen. Die andere Art wären "Service" -Parameter. Dies sind Klassen, die Geschäftslogik kapseln, die Kommunikation mit externen Systemen ermöglichen und den Datenzugriff ermöglichen.
Hier ist ein allgemeiner Workflow meiner Architektur anhand eines Beispiels. Nehmen wir an, wir haben eine FriendsViewController
, die eine Liste der Freunde des Benutzers anzeigt, und wir haben eine Option zum Entfernen von Freunden. Ich erstelle in meiner FriendsServices
Klasse eine Methode namens:
- (RACSignal *)removeFriend:(Friend * const)friend
Wo Friend
ist ein Modell- / Domänenobjekt (oder es kann nur ein User
Objekt sein, wenn sie ähnliche Attribute haben). Unter der Motorhaube dieser Methode parst , Friend
um NSDictionary
von JSON Parametern friend_id
, name
, surname
, friend_request_id
und so weiter. Ich verwende die Mantle- Bibliothek immer für diese Art von Boilerplate und für meine Modellebene (Parsen vor und zurück, Verwalten verschachtelter Objekthierarchien in JSON usw.). Nach dem Parsen ruft APIClient
DELETE
Methode eine tatsächliche REST Anfrage und kehrt zu machen Response
in RACSignal
den Anrufer ( FriendsViewController
in unserem Fall) für den Benutzer oder was auch immer entsprechende Meldung angezeigt werden soll .
Wenn unsere Anwendung sehr groß ist, müssen wir unsere Logik noch klarer trennen. Zum Beispiel ist es nicht immer gut, Repository
Logik mit Service
einer zu mischen oder zu modellieren . Als ich meinen Ansatz beschrieb, hatte ich gesagt, dass die removeFriend
Methode in der Service
Ebene sein sollte, aber wenn wir pedantischer sind, können wir feststellen, dass sie besser dazu gehört Repository
. Erinnern wir uns, was Repository ist. Eric Evans gab es eine genaue Beschreibung in seinem Buch [DDD]:
Ein Repository repräsentiert alle Objekte eines bestimmten Typs als konzeptionelle Menge. Es verhält sich wie eine Sammlung, außer mit einer detaillierteren Abfragefunktion.
A Repository
ist also im Wesentlichen eine Fassade, die die Semantik im Sammlungsstil (Hinzufügen, Aktualisieren, Entfernen) verwendet, um den Zugriff auf Daten / Objekte zu ermöglichen. Das ist der Grund, warum Sie, wenn Sie so etwas wie : getFriendsList
, haben getUserGroups
, removeFriend
es in das platzieren können Repository
, da die sammlungsähnliche Semantik hier ziemlich klar ist. Und Code wie:
- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;
ist definitiv eine Geschäftslogik, da sie über grundlegende CRUD
Operationen hinausgeht und zwei Domänenobjekte ( Friend
und Request
) verbindet. Deshalb sollte sie in der Service
Ebene platziert werden. Auch ich möchte beachten: Erstellen Sie keine unnötigen Abstraktionen . Verwenden Sie alle diese Ansätze mit Bedacht aus. Denn wenn Sie Ihre Anwendung mit Abstraktionen überfordern, erhöht dies ihre zufällige Komplexität, und Komplexität verursacht mehr Probleme in Softwaresystemen als alles andere
Ich beschreibe Ihnen ein "altes" Objective-C-Beispiel, aber dieser Ansatz kann sehr einfach für die Swift-Sprache angepasst werden, mit viel mehr Verbesserungen, da er nützlichere Funktionen und funktionalen Zucker enthält. Ich empfehle dringend, diese Bibliothek zu verwenden: Moya . Sie können damit eine elegantere APIClient
Ebene erstellen (unser Arbeitstier, wie Sie sich erinnern). Jetzt wird unser APIClient
Anbieter ein Wertetyp (enum) mit protokollkonformen Erweiterungen sein, der den Destrukturierungsmusterabgleich nutzt. Schnelle Enums + Pattern Matching ermöglichen es uns, algebraische Datentypen wie bei der klassischen funktionalen Programmierung zu erstellen . Unsere Microservices werden diesen verbesserten APIClient
Anbieter wie beim üblichen Objective-C-Ansatz verwenden. Für die Modellebene Mantle
können Sie stattdessen die ObjectMapper-Bibliothek verwendenoder ich verwende gerne eine elegantere und funktionalere Argo- Bibliothek.
Also habe ich meinen allgemeinen architektonischen Ansatz beschrieben, der meiner Meinung nach für jede Anwendung angepasst werden kann. Natürlich kann es noch viel mehr Verbesserungen geben. Ich rate Ihnen, funktionale Programmierung zu lernen, weil Sie viel davon profitieren können, aber nicht zu weit damit gehen. Das Eliminieren eines übermäßigen, gemeinsamen, global veränderlichen Zustands, das Erstellen eines unveränderlichen Domänenmodells oder das Erstellen reiner Funktionen ohne externe Nebenwirkungen ist im Allgemeinen eine gute Praxis, und eine neue Swift
Sprache fördert dies. Denken Sie jedoch immer daran, dass das Überladen Ihres Codes mit starken reinen Funktionsmustern und kategorietheoretischen Ansätzen eine schlechte Idee ist, da andere Entwickler Ihren Code lesen und unterstützen und sie frustriert oder beängstigend sein könnenprismatic profunctors
und solche Sachen in Ihrem unveränderlichen Modell. Das Gleiche gilt für ReactiveCocoa
: Nicht zu viel , da es besonders für Neulinge sehr schnell unlesbar werden kann. Verwenden Sie es, wenn es Ihre Ziele und Ihre Logik wirklich vereinfachen kann.
RACify
Ihren Code nicht
Also , read a lot, mix, experiment, and try to pick up the best from different architectural approaches
. Es ist der beste Rat, den ich Ihnen geben kann.