The Use and Adaptation of the VIPER Architecture on Our Own Example of Development, part 4

We’re happy to bring to your attention the last part of the article series on using and adapting the Viper architecture. In the first and second parts, we were talking about the basics and structuring project skeleton for VIPER. And last time we’d discussed creating a module in terms of VIPER architecture. Now we’re going to deal with the most difficult topic: VIPER example with Alamofire+PromiseKit+ObjectMapper.

Let’s consider it in order.

APIService

There is a rather popular issue regarding API service implementation, namely coverage of all the logic by a single class. It leads to the service overload that makes fixing, testing and adding new elements processes very hard. Let’s see how we can separate responsibility between several classes (RequestBuilder, RequestExecutor, APIService).

1. RequestBuilder creates requests with parameters that should be passed to the Method

2. RequestExecutor runs requests built with RequestBuilder and processes the response

3. RequestExecutorExtension defines all possible types of requests and methods of handling them (e.g the response is a mappable object, an array of mappable objects or other elements like String, Int, etc). So, it will be a mediator between APIService and core logic of the RequestExecutor.

4. APIService is a protocol which defines all possible methods it’s responsible for.

5. APIServiceImpl is APIService implementation which calls RequestExecutor using the request created by RequestBuilder.

We use PromiseKit for APIService.

PromiseKit is a thoughtful and complete implementation of Promises for iOS and macOS platforms that has first-class support for both Objective-C and Swift.

  • Powerful. Promises transform asynchronous operations into composable flexible objects.
  • Easy. Promises make developing with asynchronicity very easy.
  • Delightful. Promises for Foundation, UIKit, etc. make iOS developing a dream come true.

We won’t take a look in prses in details, so if you want to learn more, see PromiseKit GitHub page or PromiseKit Documentation

Put it shortly, promises allow us to make asynchronous requests very easy to use.

We also use ObjectMapper for mapping the response into models applying a “map” method. The model should conform the “Mappable” protocol and define two methods: required init?(map: Map) and func mapping(map: Map). For Instance:

Now let’s take a look at our example of the app which shows the weather in different cities. We have two modules here: StartScreen and DetailsScreen. StartScreen contains the list of cities, DetailsScreen is responsible for displaying the weather in a selected city.

ApplicationRoot is responsible for starting the application; it also chooses the module that should be displayed first. In our case, it is StartScreen.

Let’s examine the VIPER module using the example.

When StartScreen controller is loaded it calls Presenter by StartScreenViewOutput and informs that View is ready by calling func viewIsReady(). It could perform a fetch request, for instance, or anything else needed after View has been loaded.

Then, Сontroller needs cities to show on the screen, so StartScreenViewOutput defines var cities: [City] { get } and doesn’t care how presenter will get them. It’s used as a usual variable called by a function:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
return output.cities.count 
}

In this example, we have a hardcoded list of cities, so there is no need to wait until it’s retrieved from the web. Below, we will describe how to work with async API request.

Presenter communicates with Interactor by the next protocol:

Interactor doesn’t contain any information and decides where to retrieve data (e.g. to call a specific service or obtain data from database, etc.). In our example, cities are stored in WeatherService and Interactor has a reference to it (as it’s configured with required cities).

WeatherService conforms to

and implementation

As we can see, each item is responsible for a specific task. ViewController shows the data on View, Presenter retrieves data from Interactor and prepares it for View (our example is easy enough and works as proxy). Interactor is the only one knowing where the information needed could be gotten from, and Service is responsible for the data.

When a user selects a city, View calls Presenter to show the next screen with weather details:

And Presenter decides how to show it (push/present etc) and calls corresponding method in AppRouter.

Here Presenter should create module “urn” to allow the router to push it. Let’s see DetailsScreenFactory in more details.

var moduleURN: String { return "DetailsScreen:{cityId}" }

As we can see, this module has a parameter “cityId” needed to know a selected city. It has a method that creates urn with this parameter

Then, AppRouter creates the module by urn and pushes it.

Now, when DetailsScreen module has been shown, let’s take a look at ViewInput and ViewOutput protocols.

We have two standard methods setupInitialState and viewIsReady. Also it contains

As we are going to use the asynchronous API request, we also should allow View to know when the request is executed and the weather is received. That’s why we need assignWeather method.

All views conform protocol ModuleInputProtocol

We parse the cityId parameter transferred from the previous controller.

When View is loaded we ask Presenter (output) for a city by id we previously parsed. Then we call the method viewIsReady and requests for the weather.

Let’s see how Presenter performs the asynchronous task.

We can show any activity indicator if needed before performing the request and hide it in “always” unit as it is called in both cases whether the request has succeeded or failed.

The method obtainCurrentWeather returns Promise (see PromiseKit GitHub page or PromiseKit for details), so, in “catch” unit we show an error alert meaning that the request is failed, and in “then” unit we transfer the result (the weather) to view.

Interactor is quite simple here as well.

As you can see, VIPER is not so complicated as it could appear. All the logic is separated between several classes making app testing process much more convenient.

You can find example on our GitHub page

This piece concludes our series of articles on Viper architecture. We hope you were following our blog and found something useful to yourself.


Originally published at agilie.com.