I'm an iOS developer with some experience and this question is really interesting to me. I saw a lot of different resources and materials on this topic, but nevertheless I'm still confused. What is the best architecture for an iOS networked application? I mean basic abstract framework, patterns, which will fit every networking application whether it is a small app which only have a few server requests or a complex REST client. Apple recommends to use
I want to understand basic, abstract and correct architectural approach for networking applications in iOS : there is no "the best", or "the most correct" approach for building an application architecture. It is a very creative job. You should always choose the most straightforward and extensible architecture, which will be clear for any developer, who begin to work on your project or for other developers in your team, but I agree, that there can be a "good" and a "bad" architecture.
collect the most interesting approaches from experienced iOS developers, I don't think that my approach is the most interesting or correct, but I've used it in several projects and satisfied with it. It is a hybrid approach of the ones you have mentioned above, and also with improvements from my own research efforts. I'm interesting in the problems of building approaches, which combine several well-known patterns and idioms. I think a lot of Fowler's enterprise patterns can be successfully applied to the mobile applications. Here is a list of the most interesting ones, which we can apply for creating an iOS application architecture (in my opinion): Service Layer, Unit Of Work, Remote Facade, Data Transfer Object, Gateway, Layer Supertype, Special Case, Domain Model. You should always correctly design a model layer and always don't forget about the persistence (it can significantly increase your app's performance). You can use
Core Data for this. But you should not forget, that
Core Data is not an ORM or a database, but an object graph manager with persistence as a good option of it. So, very often
Core Data can be too heavy for your needs and you can look at new solutions such as Realm and Couchbase Lite, or build your own lightweight object mapping/persistence layer, based on raw SQLite or LevelDB. Also I advice you to familiarize yourself with the Domain Driven Design and CQRS.
At first, I think, we should create another layer for networking, because we don't want fat controllers or heavy, overwhelmed models. I don't believe in those
fat model, skinny controller things. But I do believe in
skinny everything approach, because no class should be fat, ever. All networking can be generally abstracted as business logic, consequently we should have another layer, where we can put it. Service Layer is what we need:
It encapsulates the application's business logic, controlling transactions and coordinating responses in the implementation of its operations.
Service Layer is something like a mediator between domain model and controllers. There is a rather similar variation of this approach called MVCS where a
Store is actually our
Store vends model instances and handles the networking, caching etc. I want to mention that you should not write all your networking and business logic in your service layer. This also can be considered as a bad design. For more info look at the Anemic and Rich domain models. Some service methods and business logic can be handled in the model, so it will be a "rich" (with behaviour) model.
I always extensively use two libraries: AFNetworking 2.0 and ReactiveCocoa. I think it is a must have for any modern application that interacts with the network and web-services or contains complex UI logic.
At first I create a general
APIClient class, which is a subclass of AFHTTPSessionManager. This is a workhorse of all networking in the application: all service classes delegate actual REST requests to it. It contains all the customizations of HTTP client, which I need in the particular application: SSL pinning, error processing and creating straightforward
NSError objects with detailed failure reasons and descriptions of all
API and connection errors (in such case controller will be able to show correct messages for the user), setting request and response serializers, http headers and other network-related stuff. Then I logically divide all the API requests into subservices or, more correctly, microservices:
FriendsServices and so on, accordingly to business logic they implement. Each of these microservices is a separate class. They, together, form a
Service Layer. These classes contain methods for each API request, process domain models and always returns a
RACSignal with the parsed response model or
NSError to the caller.
I want to mention that if you have complex model serialisation logic - then create another layer for it: something like Data Mapper but more general e.g. JSON/XML -> Model mapper. If you have cache: then create it as a separate layer/service too (you shouldn't mix business logic with caching). It can be somewhere in the Repository.
After all these actions in the Service layer, caller (view controller) can do some complex asynchronous stuff with the response: signal manipulations, chaining, mapping, etc. with the help of
ReactiveCocoa primitives , or just subscribe to it and show results in the view. I inject with the Dependency Injection in all these service classes my
APIClient, which will translate a particular service call into corresponding
DELETE, etc. request to the REST endpoint. In this case
APIClient is passed implicitly to all controllers, you can make this explicit with a parametrised over
APIClient service classes. This can make sense if you want to use different customisations of the
APIClient for particular service classes, but if you ,for some reasons, don't want extra copies or you are sure that you always will use one particular instance (without customisations) of the
APIClient - make it a singleton, but DON'T, please DON'T make service classes as singletons.
Then each view controller again with the DI injects the service class it needs, calls appropriate service methods and composes their results with the UI logic. For dependency injection I like to use BloodMagic or a more powerful framework Typhoon. I never use singletons, God
APIManagerWhatever class or other wrong stuff. Because if you call your class
WhateverManager, this indicates than you don't know its purpose and it is a bad design choice. Singletons is also an anti-pattern, and in most cases (except rare ones) is a wrong solution. Singleton should be considered only if all three of the following criteria are satisfied:
In our case ownership of the single instance is not an issue and also we don't need global access after we divided our god manager into services, because now only one or several dedicated controllers need a particular service (e.g.
UserProfile controller needs
UserServices and so on).
We should always respect
S principle in SOLID and use separation of concerns, so don't put all your service methods and networks calls in one class, because it's crazy, especially if you develop a large enterprise application. That's why we should consider dependency injection and services approach. I consider this approach as modern and post-OO. In this case we split our application into two parts: control logic (controllers and events) and parameters.
One kind of parameters would be ordinary “data” parameters. That’s what we pass around functions, manipulate, modify, persist, etc. These are entities, aggregates, collections, case classes. The other kind would be “service” parameters. These are classes which encapsulate business logic, allow communicating with external systems, provide data access.
Here is a general workflow of my architecture by example. Let's suppose we have a
FriendsViewController, which displays list of user's friends and we have an option to remove from friends. I create a method in my
FriendsServices class called:
- (RACSignal *)removeFriend:(Friend * const)friend
Friend is a model/domain object (or it can be just a
User object if they have similar attributes). Underhood this method parses
NSDictionary of JSON parameters
friend_request_id and so on. I always use Mantle library for this kind of boilerplate and for my model layer (parsing back and forward, managing nested object hierarchies in JSON and so on). After parsing it calls
DELETE method to make an actual REST request and returns
RACSignal to the caller (
FriendsViewController in our case) to display appropriate message for the user or whatever.
If our application is a very big one, we have to separate our logic even clearer. E.g. it is not always good to mix
Repository or model logic with
Service one. When I described my approach I had said that
removeFriend method should be in the
Service layer, but if we will be more pedantic we can notice that it better belongs to
Repository. Let's remember what Repository is. Eric Evans gave it a precise description in his book [DDD]:
A Repository represents all objects of a certain type as a conceptual set. It acts like a collection, except with more elaborate querying capability.
Repository is essentially a facade that uses Collection style semantics (Add, Update, Remove) to supply access to data/objects. That's why when you have something like:
removeFriend you can place it in the
Repository, because collection-like semantics is pretty clear here. And code like:
- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;
is definitely a business logic, because it is beyond basic
CRUD operations and connect two domain objects (
Request), that's why it should be placed in the
Service layer. Also I want to notice: don't create unnecessary abstractions. Use all these approaches wisely. Because if you will overwhelm your application with abstractions, this will increase its accidental complexity, and complexity causes more problems in software systems than anything else
I describe you an "old" Objective-C example but this approach can be very easy adapted for Swift language with a lot more improvements, because it has more useful features and functional sugar. I highly recommend to use this library: Moya. It allows you to create a more elegant
APIClient layer (our workhorse as you remember). Now our
APIClient provider will be a value type (enum) with extensions conforming to protocols and leveraging destructuring pattern matching. Swift enums + pattern matching allows us to create algebraic data types as in classic functional programming. Our microservices will use this improved
APIClient provider as in usual Objective-C approach. For model layer instead of
Mantle you can use ObjectMapper library or I like to use more elegant and functional Argo library.
So, I described my general architectural approach, which can be adapted for any application, I think. There can be a lot more improvements, of course. I advice you to learn functional programming, because you can benefit from it a lot, but don't go too far with it too. Eliminating excessive, shared, global mutable state, creating an immutable domain model or creating pure functions without external side-effects is, generally, a good practice, and new
Swift language encourages this. But always remember, that overloading your code with heavy pure functional patterns, category-theoretical approaches is a bad idea, because other developers will read and support your code, and they can be frustrated or scary of the
prismatic profunctors and such kind of stuff in your immutable model. The same thing with the
RACify your code too much, because it can become unreadable really fast, especially for newbies. Use it when it can really simplify your goals and logic.
read a lot, mix, experiment, and try to pick up the best from different architectural approaches. It is the best advice I can give you.