Ich bin ein iOS-Entwickler mit etwas Erfahrung und diese Frage ist wirklich interessant für mich. Ich habe viele verschiedene Ressourcen und Materialien zu diesem Thema gesehen, bin aber trotzdem verwirrt. Was ist die beste Architektur für eine iOS-Netzwerkanwendung? Ich meine grundlegende abstrakte Frameworks, Muster, die zu jeder Netzwerkanwendung passen, egal ob es sich um eine kleine App mit nur wenigen Serveranforderungen oder einen komplexen REST-Client handelt. Apple empfiehlt, MVC
als grundlegenden Architekturansatz für alle iOS-Anwendungen zu verwenden, jedoch weder für MVC
die modernerenMVVM
Muster erklären, wo Netzwerklogikcode abgelegt und wie er allgemein organisiert werden soll.
Muss ich so etwas wie MVCS
( S
für Service
) entwickeln und in diese Service
Ebene alle API
Anforderungen und andere Netzwerklogiken einfügen, die in der Perspektive sehr komplex sein können? Nach einigen Recherchen habe ich zwei grundlegende Ansätze dafür gefunden. Hier wurde empfohlen, für jede Netzwerkanforderung an den Webdienst API
(wie LoginRequest
Klasse oder PostCommentRequest
Klasse usw.) eine separate Klasse zu erstellen, die alle von der abstrakten Klassenoptimierung der Basisanforderung erbt , wenn komplexe Objektzuordnungen und Persistenz vorliegen, oder sogar eine eigene Netzwerkkommunikationsimplementierung mit Standard-API). Aber dieser Ansatz scheint mir ein Overhead zu sein. Ein anderer Ansatz besteht darin, eine Singleton- Dispatcher- oder Manager-Klasse wie im ersten Ansatz zu haben.AbstractBaseRequest
und zusätzlich einen globalen Netzwerkmanager zu erstellen, der gemeinsamen Netzwerkcode und kapselt andere Einstellungen (es kann AFNetworking
Anpassung sein oderRestKit
API
jedoch nicht für jede Anforderung Klassen zu erstellen und stattdessen jede Anforderung als öffentliche Instanzmethode dieser Manager-Klasse wie : fetchContacts
, loginUser
Methoden usw. zu kapseln . Also, was ist der beste und richtige Weg? Gibt es andere interessante Ansätze, die ich noch nicht kenne?
Und sollte ich eine weitere Ebene für all diese Netzwerk-Inhalte wie Service
oder eine NetworkProvider
Ebene oder was auch immer über meiner MVC
Architektur erstellen, oder sollte diese Ebene in vorhandene MVC
Ebenen integriert (injiziert) werden , z Model
.
Ich weiß, dass es schöne Ansätze gibt, oder wie gehen solche mobilen Monster wie der Facebook-Client oder der LinkedIn-Client mit der exponentiell wachsenden Komplexität der Netzwerklogik um?
Ich weiß, dass es keine genaue und formale Antwort auf das Problem gibt. Ziel dieser Frage ist es, die interessantesten Ansätze von erfahrenen iOS-Entwicklern zu sammeln . Der am besten vorgeschlagene Ansatz wird als akzeptiert markiert und mit einer Reputationsprämie ausgezeichnet, andere werden positiv bewertet. Es ist meist eine theoretische und Forschungsfrage. Ich möchte den grundlegenden, abstrakten und korrekten Architekturansatz für Netzwerkanwendungen in iOS verstehen. Ich hoffe auf eine ausführliche Erklärung von erfahrenen Entwicklern.
quelle
Antworten:
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önnenCore Data
dies verwenden. Sie sollten jedoch nicht vergessen, dass diesCore Data
kein ORM oder eine Datenbank ist, sondern ein Objektdiagramm-Manager mit Persistenz als gute Option. Daher kann es sehr oftCore 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 anskinny 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:In unserem
MVC
BereichService Layer
ist so etwas wie ein Vermittler zwischen Domänenmodell und Controllern. Es gibt eine ziemlich ähnliche Variante dieses Ansatzes namens MVCS, bei der aStore
tatsächlich unsereService
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 einfacherNSError
Objekte mit detaillierten Fehlergründen und Beschreibungen allerAPI
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 eineService Layer
. Diese Klassen enthalten Methoden für jede API-Anforderung, verarbeiten Domänenmodelle und geben immer eineRACSignal
mit dem analysierten Antwortmodell oderNSError
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, wieNSFetchRequest
,NSEntityDescription
,NSPredicate
und so weiter in einfachen Methoden wieget
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 meinenAPIClient
, die einen bestimmten Service - Aufruf in entsprechenden übersetzen werdenGET
,POST
,PUT
,DELETE
etc. Anfrage an den REST - Endpunkt. In diesem FallAPIClient
wird implizit an alle Controller übergeben, Sie können dies mit einer überAPIClient
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) vonAPIClient
- 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 anrufenWhateverManager
, 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: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-AnforderungenUserServices
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.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 meinerFriendsServices
Klasse eine Methode namens:Wo
Friend
ist ein Modell- / Domänenobjekt (oder es kann nur einUser
Objekt sein, wenn sie ähnliche Attribute haben). Unter der Motorhaube dieser Methode parst ,Friend
umNSDictionary
von JSON Parameternfriend_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 ruftAPIClient
DELETE
Methode eine tatsächliche REST Anfrage und kehrt zu machenResponse
inRACSignal
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 mitService
einer zu mischen oder zu modellieren . Als ich meinen Ansatz beschrieb, hatte ich gesagt, dass dieremoveFriend
Methode in derService
Ebene sein sollte, aber wenn wir pedantischer sind, können wir feststellen, dass sie besser dazu gehörtRepository
. Erinnern wir uns, was Repository ist. Eric Evans gab es eine genaue Beschreibung in seinem Buch [DDD]: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
, habengetUserGroups
,removeFriend
es in das platzieren könnenRepository
, da die sammlungsähnliche Semantik hier ziemlich klar ist. Und Code wie:ist definitiv eine Geschäftslogik, da sie über grundlegende
CRUD
Operationen hinausgeht und zwei Domänenobjekte (Friend
undRequest
) verbindet. Deshalb sollte sie in derService
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 andereIch 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 unserAPIClient
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 verbessertenAPIClient
Anbieter wie beim üblichen Objective-C-Ansatz verwenden. Für die ModellebeneMantle
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ürReactiveCocoa
: 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 nichtAlso ,
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.quelle
". I don't like singletons. I have an opinion that if you decided to use singletons in your project you should have at least three criteria why you do this (I edited my answer). So I inject them (lazy of course and not each time, but
einmal `) in jedem Controller.Entsprechend dem Ziel dieser Frage möchte ich unseren Architekturansatz beschreiben.
Architekturansatz
Die Architektur unserer allgemeinen iOS-Anwendung basiert auf folgenden Mustern: Service-Layer , MVVM , UI-Datenbindung , Abhängigkeitsinjektion ; und funktionales reaktives Programmierparadigma .
Wir können eine typische Consumer-Anwendung in folgende logische Ebenen unterteilen:
Die Assembly-Schicht ist ein Bootstrap-Punkt unserer Anwendung. Es enthält einen Container für Abhängigkeitsinjektionen und Deklarationen der Anwendungsobjekte und ihrer Abhängigkeiten. Diese Schicht kann auch die Konfiguration der Anwendung enthalten (URLs, Dienstschlüssel von Drittanbietern usw.). Zu diesem Zweck verwenden wir die Typhoon- Bibliothek.
Die Modellebene enthält Klassen, Validierungen und Zuordnungen für Domänenmodelle. Wir verwenden die Mantle- Bibliothek für die Zuordnung unserer Modelle: Sie unterstützt die Serialisierung / Deserialisierung in
JSON
Format undNSManagedObject
Modelle. Zur Validierung und Formulardarstellung unserer Modelle verwenden wir die Bibliotheken FXForms und FXModelValidation .Die Serviceschicht deklariert Services, die wir für die Interaktion mit externen Systemen verwenden, um Daten zu senden oder zu empfangen, die in unserem Domänenmodell dargestellt sind. Normalerweise verfügen wir über Dienste für die Kommunikation mit Server-APIs (pro Entität), Messaging-Diensten (wie PubNub ), Speicherdiensten (wie Amazon S3) usw. Grundsätzlich verpacken Dienste Objekte, die von SDKs bereitgestellt werden (z. B. PubNub SDK), oder implementieren ihre eigene Kommunikation Logik. Für die allgemeine Vernetzung verwenden wir die AFNetworking- Bibliothek.
Speicherschicht ‚s Zweck ist die lokale Datenspeicherung auf dem Gerät zu organisieren. Wir verwenden hierfür Core Data oder Realm (beide haben Vor- und Nachteile, die Entscheidung über die Verwendung basiert auf konkreten Spezifikationen). Für die Einrichtung der Kerndaten verwenden wir die MDMCoreData- Bibliothek und eine Reihe von Klassen - Speicher - (ähnlich wie Dienste), die für jede Entität Zugriff auf den lokalen Speicher bieten. Für Realm verwenden wir nur ähnliche Speicher, um Zugriff auf den lokalen Speicher zu erhalten.
Die Manager-Ebene ist ein Ort, an dem unsere Abstraktionen / Wrapper leben.
In einer Manager-Rolle könnte sein:
Die Rolle des Managers kann also jedes Objekt sein, das die Logik eines bestimmten Aspekts oder Anliegens implementiert, das für die Anwendungsarbeit erforderlich ist.
Wir versuchen, Singletons zu vermeiden, aber diese Schicht ist ein Ort, an dem sie leben, wenn sie gebraucht werden.
Die Ebene "Koordinatoren" stellt Objekte bereit, die von Objekten aus anderen Ebenen (Service, Speicher, Modell) abhängen, um ihre Logik in einer Arbeitssequenz zu kombinieren, die für ein bestimmtes Modul (Feature, Bildschirm, User Story oder Benutzererfahrung) erforderlich ist. Es verkettet normalerweise asynchrone Vorgänge und weiß, wie auf Erfolgs- und Fehlerfälle zu reagieren ist. Als Beispiel können Sie sich eine Nachrichtenfunktion und ein entsprechendes
MessagingCoordinator
Objekt vorstellen . Die Behandlung des Sendevorgangs kann folgendermaßen aussehen:Bei jedem der obigen Schritte wird ein Fehler entsprechend behandelt.
Die UI-Ebene besteht aus folgenden Unterschichten:
Um Massive View Controller zu vermeiden, verwenden wir MVVM-Muster und implementieren die für die UI-Präsentation in ViewModels erforderliche Logik. Ein ViewModel hat normalerweise Koordinatoren und Manager als Abhängigkeiten. ViewModels, die von ViewControllern und einigen Arten von Ansichten verwendet werden (z. B. Tabellenansichtszellen). Der Klebstoff zwischen ViewControllern und ViewModels ist Datenbindung und Befehlsmuster. Um diesen Kleber zu erhalten, verwenden wir die ReactiveCocoa- Bibliothek.
Wir verwenden ReactiveCocoa und sein
RACSignal
Konzept auch als Schnittstelle und Rückgabewert aller Koordinatoren, Dienste und Speichermethoden. Auf diese Weise können wir Vorgänge verketten, parallel oder seriell ausführen und viele andere nützliche Dinge, die von ReactiveCocoa bereitgestellt werden.Wir versuchen, unser UI-Verhalten deklarativ zu implementieren. Datenbindung und automatisches Layout tragen viel dazu bei, dieses Ziel zu erreichen.
Die Infrastrukturschicht enthält alle Helfer, Erweiterungen und Dienstprogramme, die für die Anwendungsarbeit erforderlich sind.
Dieser Ansatz funktioniert gut für uns und die Arten von Apps, die wir normalerweise erstellen. Aber Sie sollten verstehen, dass dies nur ein subjektiver Ansatz ist, der sollte für Beton - Team Zweck angepasst / verändert werden.
Hoffe das wird dir helfen!
Weitere Informationen zum iOS-Entwicklungsprozess finden Sie in diesem Blogbeitrag iOS Development as a Service
quelle
Da alle iOS-Apps unterschiedlich sind, sollten hier unterschiedliche Ansätze berücksichtigt werden. In der Regel gehe ich jedoch folgendermaßen vor:
Erstellen Sie eine zentrale Manager-Klasse (Singleton), um alle API-Anforderungen (normalerweise APICommunicator) zu verarbeiten, und jede Instanzmethode ist ein API-Aufruf . Und es gibt eine zentrale (nicht öffentliche) Methode:
Für die Aufzeichnung verwende ich 2 Hauptbibliotheken / Frameworks, ReactiveCocoa und AFNetworking. ReactiveCocoa verarbeitet asynchrone Netzwerkantworten perfekt (sendNext:, sendError: usw.).
Diese Methode ruft die API auf, ruft die Ergebnisse ab und sendet sie im Rohformat über RAC (wie NSArray, was AFNetworking zurückgibt).
Dann
getStuffList:
abonniert eine Methode wie die oben genannte Methode das Signal, analysiert die Rohdaten in Objekte (mit etwas wie Motis) und sendet die Objekte einzeln an den Aufrufer (getStuffList:
und ähnliche Methoden geben auch ein Signal zurück, das der Controller abonnieren kann ).Der abonnierte Controller empfängt die Objekte per
subscribeNext:
Block und verarbeitet sie.Ich habe viele Möglichkeiten in verschiedenen Apps ausprobiert, aber diese hat am besten funktioniert, daher habe ich sie kürzlich in einigen Apps verwendet. Sie eignet sich sowohl für kleine als auch für große Projekte und ist einfach zu erweitern und zu warten, wenn etwas geändert werden muss.
Hoffe das hilft, ich würde gerne die Meinungen anderer über meinen Ansatz hören und vielleicht, wie andere denken, dass dies vielleicht verbessert werden könnte.
quelle
+ (void)getAllUsersWithSuccess:(void(^)(NSArray*))success failure:(void(^)(NSError*))failure;
und bereit,- (void)postWithSuccess:(void(^)(instancetype))success failure:(void(^)(NSError*))failure;
die die erforderlichen Vorbereitungen treffen und dann den API-Manager aufrufen.In meiner Situation verwende ich normalerweise die ResKit- Bibliothek, um die Netzwerkschicht einzurichten. Es bietet eine benutzerfreundliche Analyse. Dies reduziert meinen Aufwand beim Einrichten des Mappings für verschiedene Antworten und Dinge.
Ich füge nur Code hinzu, um das Mapping automatisch einzurichten. Ich definiere die Basisklasse für meine Modelle (kein Protokoll, da viel Code vorhanden ist, um zu überprüfen, ob eine Methode implementiert ist oder nicht, und weniger Code in den Modellen selbst):
MappableEntry.h
MappableEntry.m
Beziehungen sind Objekte, die als Antwort verschachtelte Objekte darstellen:
RelationshipObject.h
RelationshipObject.m
Dann richte ich das Mapping für RestKit folgendermaßen ein:
ObjectMappingInitializer.h
ObjectMappingInitializer.m
Ein Beispiel für die Implementierung von MappableEntry:
User.h
User.m
Nun zum Wrapping der Anfragen:
Ich habe eine Header-Datei mit Blockdefinition, um die Zeilenlänge in allen APIRequest-Klassen zu reduzieren:
APICallbacks.h
Und Beispiel meiner APIRequest-Klasse, die ich verwende:
LoginAPI.h
LoginAPI.m
Und alles, was Sie im Code tun müssen, initialisieren Sie einfach das API-Objekt und rufen Sie es auf, wann immer Sie es benötigen:
SomeViewController.m
Mein Code ist nicht perfekt, aber es ist einfach, ihn einmal festzulegen und für verschiedene Projekte zu verwenden. Wenn es für jemanden interessant ist, könnte ich einige Zeit damit verbringen, irgendwo auf GitHub und CocoaPods eine universelle Lösung dafür zu finden.
quelle
Meiner Meinung nach wird die gesamte Softwarearchitektur von den Anforderungen bestimmt. Wenn dies zu Lern- oder persönlichen Zwecken dient, entscheiden Sie das primäre Ziel und lassen Sie die Architektur steuern. Wenn es sich um eine Mietarbeit handelt, ist der geschäftliche Bedarf von größter Bedeutung. Der Trick besteht darin, sich nicht von glänzenden Dingen von den tatsächlichen Bedürfnissen ablenken zu lassen. Ich finde das schwer zu tun. Es gibt immer neue glänzende Dinge in diesem Geschäft und viele davon sind nicht nützlich, aber das kann man nicht immer im Voraus sagen. Konzentrieren Sie sich auf die Notwendigkeit und seien Sie bereit, schlechte Entscheidungen aufzugeben, wenn Sie können.
Zum Beispiel habe ich kürzlich einen kurzen Prototyp einer Foto-Sharing-App für ein lokales Unternehmen erstellt. Da das Geschäftsbedürfnis darin bestand, schnell und schmutzig zu arbeiten, bestand die Architektur aus iOS-Code zum Aufrufen einer Kamera und Netzwerkcode, der an eine Senden-Schaltfläche angehängt war, mit der das Bild in einen S3-Speicher hochgeladen und in eine SimpleDB-Domäne geschrieben wurde. Der Code war trivial und die Kosten minimal, und der Client verfügt über eine skalierbare Fotosammlung, auf die über das Web mit REST-Aufrufen zugegriffen werden kann. Günstig und dumm, die App hatte viele Fehler und sperrte gelegentlich die Benutzeroberfläche, aber es wäre eine Verschwendung, mehr für einen Prototyp zu tun, und es ermöglicht ihnen, sie für ihre Mitarbeiter bereitzustellen und Tausende von Testbildern einfach ohne Leistung oder Skalierbarkeit zu generieren Sorgen. Beschissene Architektur, aber sie passte perfekt zu den Bedürfnissen und Kosten.
Ein weiteres Projekt umfasste die Implementierung einer lokalen sicheren Datenbank, die im Hintergrund mit dem Unternehmenssystem synchronisiert wird, wenn das Netzwerk verfügbar ist. Ich habe einen Hintergrundsynchronisierer erstellt, der RestKit verwendet, da es anscheinend alles enthält, was ich brauche. Aber ich musste so viel benutzerdefinierten Code für RestKit schreiben, um mit eigenwilligem JSON umgehen zu können, dass ich alles schneller hätte tun können, indem ich meinen eigenen JSON in CoreData-Transformationen geschrieben hätte. Der Kunde wollte diese App jedoch ins Haus bringen, und ich war der Meinung, dass RestKit den Frameworks ähneln würde, die sie auf anderen Plattformen verwendeten. Ich warte darauf, ob das eine gute Entscheidung war.
Auch hier geht es mir darum, mich auf die Notwendigkeit zu konzentrieren und die Architektur bestimmen zu lassen. Ich versuche höllisch, die Verwendung von Paketen von Drittanbietern zu vermeiden, da diese Kosten verursachen, die erst entstehen, nachdem die App eine Weile im Feld war. Ich versuche zu vermeiden, Klassenhierarchien zu erstellen, da sie sich selten auszahlen. Wenn ich in angemessener Zeit etwas schreiben kann, anstatt ein Paket zu übernehmen, das nicht perfekt passt, dann mache ich es. Mein Code ist für das Debuggen gut strukturiert und angemessen kommentiert, Pakete von Drittanbietern jedoch selten. Trotzdem finde ich AF Networking zu nützlich, um es zu ignorieren und gut zu strukturieren, gut zu kommentieren und zu pflegen, und ich benutze es oft! RestKit deckt viele häufige Fälle ab, aber ich habe das Gefühl, dass ich mich in einem Kampf befunden habe, wenn ich es benutze. und die meisten Datenquellen, auf die ich stoße, sind voller Macken und Probleme, die am besten mit benutzerdefiniertem Code behandelt werden können. In meinen letzten Apps verwende ich nur die integrierten JSON-Konverter und schreibe einige Dienstprogrammmethoden.
Ein Muster, das ich immer verwende, besteht darin, die Netzwerkanrufe vom Hauptthread zu entfernen. Die letzten 4-5 Apps, die ich ausgeführt habe, haben mit dispatch_source_create eine Hintergrund-Timer-Aufgabe eingerichtet, die von Zeit zu Zeit aufwacht und Netzwerkaufgaben nach Bedarf ausführt. Sie müssen einige Thread-Sicherheitsarbeiten durchführen und sicherstellen, dass der Code zum Ändern der Benutzeroberfläche an den Haupt-Thread gesendet wird. Es ist auch hilfreich, das Onboarding / die Initialisierung so durchzuführen, dass sich der Benutzer nicht belastet oder verzögert fühlt. Bisher hat das ziemlich gut funktioniert. Ich schlage vor, diese Dinge zu untersuchen.
Schließlich denke ich, dass wir mit zunehmender Arbeit und der Weiterentwicklung des Betriebssystems tendenziell bessere Lösungen entwickeln. Ich habe Jahre gebraucht, um meine Überzeugung zu überwinden, dass ich Mustern und Designs folgen muss, von denen andere behaupten, dass sie obligatorisch sind. Wenn ich in einem Kontext arbeite, in dem das Teil der lokalen Religion ist, ähm, ich meine die besten Ingenieurspraktiken der Abteilung, dann folge ich den Gepflogenheiten genau, dafür bezahlen sie mich. Aber ich finde selten, dass es die optimale Lösung ist, älteren Designs und Mustern zu folgen. Ich versuche immer, die Lösung durch das Prisma der Geschäftsanforderungen zu betrachten und die Architektur so zu gestalten, dass sie dazu passt und die Dinge so einfach wie möglich halten. Wenn ich das Gefühl habe, dass dort nicht genug ist, aber alles richtig funktioniert, bin ich auf dem richtigen Weg.
quelle
Ich verwende den Ansatz, den ich von hier erhalten habe: https://github.com/Constantine-Fry/Foursquare-API-v2 . Ich habe diese Bibliothek in Swift umgeschrieben und Sie können den architektonischen Ansatz anhand dieser Teile des Codes sehen:
Grundsätzlich gibt es eine NSOperation-Unterklasse, die die NSURLRequest erstellt, die JSON-Antwort analysiert und den Rückrufblock mit dem Ergebnis zur Warteschlange hinzufügt. Die Haupt-API-Klasse erstellt NSURLRequest, initialisiert diese NSOperation-Unterklasse und fügt sie der Warteschlange hinzu.
quelle
Wir verwenden je nach Situation einige Ansätze. Für die meisten Dinge ist AFNetworking der einfachste und robusteste Ansatz, da Sie Header festlegen, mehrteilige Daten hochladen, GET, POST, PUT & DELETE verwenden können und es eine Reihe zusätzlicher Kategorien für UIKit gibt, mit denen Sie beispielsweise ein Bild festlegen können eine URL. In einer komplexen App mit vielen Anrufen abstrahieren wir dies manchmal auf eine eigene Komfortmethode, die ungefähr so aussieht:
Es gibt einige Situationen, in denen AFNetworking nicht geeignet ist, z. B. wenn Sie ein Framework oder eine andere Bibliothekskomponente erstellen, da sich AFNetworking möglicherweise bereits in einer anderen Codebasis befindet. In dieser Situation würden Sie eine NSMutableURLRequest entweder inline verwenden, wenn Sie einen einzelnen Aufruf tätigen, oder in eine Anforderungs- / Antwortklasse abstrahieren.
quelle
Ich vermeide Singletons beim Entwerfen meiner Anwendungen. Sie sind eine typische Anlaufstelle für viele Menschen, aber ich denke, Sie können anderswo elegantere Lösungen finden. Normalerweise erstelle ich meine Entitäten in CoreData und füge meinen REST-Code in eine NSManagedObject-Kategorie ein. Wenn ich zum Beispiel einen neuen Benutzer erstellen und veröffentlichen möchte, würde ich Folgendes tun:
Ich benutze RESTKit für die Objektzuordnung und initialisiere es beim Start. Ich empfinde das Weiterleiten all Ihrer Anrufe über einen Singleton als Zeitverschwendung und füge eine Menge Boilerplate hinzu, die nicht benötigt wird.
In NSManagedObject + Extensions.m:
In NSManagedObject + Networking.m:
Warum zusätzliche Hilfsklassen hinzufügen, wenn Sie die Funktionalität einer gemeinsamen Basisklasse durch Kategorien erweitern können?
Wenn Sie an detaillierteren Informationen zu meiner Lösung interessiert sind, lassen Sie es mich wissen. Ich teile gerne.
quelle
Versuchen Sie es mit https://github.com/kevin0571/STNetTaskQueue
Erstellen Sie API-Anforderungen in getrennten Klassen.
STNetTaskQueue befasst sich mit Threading und Delegieren / Rückrufen.
Erweiterbar für verschiedene Protokolle.
quelle
Aus einer rein klassenorientierten Designperspektive haben Sie normalerweise so etwas:
Datenmodellklasse - Es hängt wirklich davon ab, mit wie vielen realen unterschiedlichen Entitäten Sie es zu tun haben und wie sie zusammenhängen.
Wenn Sie beispielsweise über ein Array von Elementen verfügen, die in vier verschiedenen Darstellungen (Liste, Diagramm, Grafik usw.) angezeigt werden sollen, verfügen Sie über eine Datenmodellklasse für die Liste der Elemente und eine weitere für ein Element. Die Liste der Elementklassen wird von vier Ansichts-Controllern gemeinsam genutzt - allen untergeordneten Elementen eines Registerkarten-Controllers oder eines Navigations-Controllers.
Datenmodellklassen sind praktisch, um Daten nicht nur anzuzeigen, sondern auch zu serialisieren, wobei jede von ihnen ihr eigenes Serialisierungsformat über JSON / XML / CSV-Exportmethoden (oder andere Exportmethoden) verfügbar machen kann.
Es ist wichtig zu verstehen, dass Sie auch API-Anforderungsgeneratorklassen benötigen , die direkt Ihren REST-API-Endpunkten zugeordnet sind. Angenommen, Sie haben eine API, die den Benutzer anmeldet. Ihre Builder-Klasse für die Anmelde-API erstellt also POST-JSON-Nutzdaten für die Anmelde-API. In einem anderen Beispiel erstellt eine API-Anforderungsgeneratorklasse für eine Liste von Katalogelement-APIs eine GET-Abfragezeichenfolge für die entsprechende API und löst die REST-GET-Abfrage aus.
Diese API-Anforderungsgeneratorklassen empfangen normalerweise Daten von Ansichtscontrollern und geben dieselben Daten auch an Ansichtscontroller für UI-Aktualisierungen / andere Vorgänge zurück. View Controller entscheiden dann, wie Datenmodellobjekte mit diesen Daten aktualisiert werden.
Schließlich das Herz der REST - Client - API - Daten Abholer Klasse , die für alle Arten von API nicht bewusst ist fordert Ihre Anwendung macht. Diese Klasse wird eher ein Singleton sein, aber wie andere betonten, muss es kein Singleton sein.
Beachten Sie, dass der Link nur eine typische Implementierung ist und keine Szenarien wie Sitzungen, Cookies usw. berücksichtigt. Es reicht jedoch aus, um Sie ohne Verwendung von Frameworks von Drittanbietern zum Laufen zu bringen.
quelle
Diese Frage hat bereits viele ausgezeichnete und ausführliche Antworten, aber ich denke, ich muss sie erwähnen, da es sonst niemand hat.
Alamofire für Swift. https://github.com/Alamofire/Alamofire
Es wurde von denselben Personen wie AFNetworking erstellt, ist jedoch direkter für Swift konzipiert.
quelle
Ich denke, für den Moment verwenden mittlere Projekte MVVM-Architektur und große Projekte VIPER-Architektur und versuchen zu erreichen
Und architektonische Ansätze zum Erstellen von iOS-Netzwerkanwendungen (REST-Clients)
Trennungsbedenken für sauberen und lesbaren Code vermeiden Doppelarbeit:
Abhängigkeitsinversion
Hauptverantwortlicher:
Hier finden Sie die GitHub MVVM-Architektur mit Rest API Swift Project
quelle
In der mobilen Softwareentwicklung werden am häufigsten Clean Architecture + MVVM- und Redux-Muster verwendet.
Clean Architecture + MVVM besteht aus 3 Ebenen: Domäne, Präsentation, Datenebenen. Wo die Präsentationsschicht und die Datenrepository-Schicht von der Domänenschicht abhängen:
Die Präsentationsschicht besteht aus ViewModels und Views (MVVM):
In diesem Artikel finden Sie eine detailliertere Beschreibung von Clean Architecture + MVVM unter https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3
quelle