A long time ago, when the first mobile apps were developed, we had really bloated view controllers. When the years passed, a lot of issues came up, such as testing, teamwork, more and more iOS frameworks, and huge projects, so we all understood architecture and layering is a key point to a successful and stable app.
The following tutorial is a methodological one. The rules aren’t incised in the rock, the principles are the same — flexibility and separation will make your code more testable.
Separate to Layers
Your code should be separated into layers, with each layer having its own responsibility in your app. Layers, meaning separate your data flow to different objects. This is extremely important because of the following reasons:
- When you have different layers, it is easier to do a co-work on your project.
- Separate your app to different components, makes it easier to maintain your code over time, as fixing bugs or refactoring one layer won’t affect the other ones.
- You can use different data models for different layers. When you need to integrate your app to frameworks such as Core Data, by working with different models, you basically isolate working the NS-Managed Object to only the core data layer. NS-Managed Object for dealing with Core Data, (DTO’s) when working with Business Logic, and Display Objects when working with UI.
- It’s much easier to write unit tests for each layer, instead of writing complicated unit tests.
There are tons of blog posts and tutorials on how to separate your app, but actually, it doesn’t really matter as long as you keep the following principles.
- Isolation — each layer should know only its neighbors, and no further. This will help in the future to refactor and change interfaces for your components.
- Protocols — Try to work as much you can with protocols, as will help you make your app testability much simpler.
- Each layer needs to have its responsibility — it sounds obvious, but it’s not. Don’t separate your code just for the sake of separation, because then you’re gonna pay for complicated code. Try to identify a specific responsibility for a specific layer, with a simple sentence.
Common Pattern of Layers for Your App
As I always say, the number of patterns out there is the number of iOS developers :) In this tutorial I will go over one example, as there are many patterns you can read about them.
Core Services -
Core services are the layer that is the closest to the OS frameworks. Its responsibility is to connect your app to the iOS frameworks, and it is the only layer that “know” those frameworks. Examples -> “Core Data Connector”, “Network Layer”, “Event Kit Connector”, “Configuration Connector”. These components usually fetch data from the framework (Such as NS-Managed Object or EK-Event) and output them as DTO’s to the next layer, so the next layer can use them without the risk of the data being deleted in the background, and it also can help with writing tests to your app.
Business Logic Services
This layer bridging between the core services to your app. It holds all the logic, and has nothing to do with (UI) whatsoever. For example, contacts service can have methods to search contacts (when connecting to the core service Address Book Connector), search the DB for a certain object (When connected to the core service Core Data Connector), or contain a method that calculates the contact age (by looking at its birth date).
Interactors are the layer that bridges the (UI) from the business logic and the core services. Everything goes through the interactor. It is common to create an interactor for every UI-Screen, to isolate it from the rest of the app.
The interactor takes the DTO’s from the business logic layer and transfers them to a display model for the UI layer. Display Model is actually the version of the DTO’s suitable for presentation, so the (UI) won’t work with DTO’s, but only with display models.
The presenter and the View Controller has reference to each other, and it’s considered to be the “real” Controller in the (MVP) pattern. So in this case, the View Controller is the “View” and contains only view related code, the presenter is the “Controller”, and interactor and the display model is the “Model”.
The presenter contains the “logic of the UI” and supposed to make decisions on issues such as “What happens when the user tap on a button”, handling states, timers, delegate of UI elements, handling life cycle, and more.
Actually, rule of thumb in the case of presenters, is that if you see an “if-then-else” statement, it probably needs to be implemented in the presenter layer.
The presenter keeps a weak reference to the View Controller, and keep its reference as a protocol only, and the view controller keeps a strong reference to the presenter.
Another reason we have a presenter, is testing. It’s much more complicated to test a method in the VC, since it may contain UI Code for an element that needs to be on-screen.
Well, I hope it’s obvious what the “view” layer represents. View layer (remember, the view controller is part of it) contains only display-related code. The only model it knows is the display model it gets from the presenter.
It doesn’t have any references to the interactor, and therefore any reference/connection to the core layer.
The view also can be separated into different classes -
- - View Controller
- Custom Views (Buttons, Labels)
The router is responsible to move the user in the app from screen to screen. Its only reference is to the presenter, therefore it’s not really a layer, but aside service that helps your code not to be messy. The router is responsible for opening of the screen it is attached to, and contains a strong reference to the transitions objects if needed.
Don’t be lazy. The communication between the objects in your layers is expected to be using protocols. This can help you to replace components, refactor and testing. When you are starting to build your UI, and some other team member works on the business logic or the server, you don’t have to wait for them. Both of you can work in parallel, since you can mock the displayed objects using the interactor. And just switch it to the real interactor when your friend is done merging his code. This can be done easily, if the communication will be based on protocols.
Let’s build a calendar app
Now for an example, let’s try to understand how to design a calendar app.
Let’s start with the core layer of the app. In order to connect to the calendar, we need to connect to an iOS framework named “Event-Kit”. Calendars and Calendar events are represented with classes name EK-Event, and EK-Calendar. Now, since we don’t want those objects to roll around our app, we are going to build some calendar connector, which will connect to Event Kit, which will take care of authorization and converting EK-Event and EK-Calendar objects to DTO’s.
The Calendar Connect layer will be responsible for:
- Fetch events and calendars, and convert them to DTO’s
- Open Native Event Screen
Since the Event-Kit-Connector responsible only to connect to the calendar, we need to create a class that will be responsible for all the logic of the calendar in the app, like:
- Caching (if needed)
- Managing the visible calendars
- Managing the default calendar
- Helper methods to check if an event in the past if it’s a multiple day’s events, etc.
- Handling outside changes for the calendar data.
We need to build interactor for each module/feature in our app, and when I say feature, I mean things like calendar screen, calendar introduction screen, event screen and so on. Here, I’m going to show you the Calendar Screen Interactor.
This Interactor will be responsible for:
- Observing changes from the Calendar service layer.
- Bridging authorization requests from UI.
- Bridging open events requests from UI.
- Mark calendars as visible and nonvisible
- Convert DTO’s to display
So the interactor is actually a proxy between the UI and business logic, and that’s why it also responsible for converting and preparing the data from the DTO’s to the display objects.
Previously, we said that the presenter is the “real view controller”, and it is the logic of the UI. Generally, we create a presenter for each screen, but it also common to create a presenter for smaller views components.
Our Calendar Screen Presenter will be responsible for:
- Get data updates from the interactor
- Handling event pressing
- Handling menu button pressing
- Handling view life cycle
- Loading more logic
The presenter will keep a reference for both the view controller, and the calendar Interactor, but also for the router.
View (View Controller)
The view controller needs to be “dump” in terms of logic, and “smart” in terms of layout and animation.
Remember we said that we need to create a protocol for each layer? Now, testing is the place where protocols really shine.
Using protocols, you can mock every layer in your project, and fully test it.
For example, let’s say you want to test the presenter, and you want to make sure that when the screen is loaded, the presenter takes the data and updates the View Controller.
So you create two (2) mocks — one for the interactor, and one for the View Controller. They both regular objects, which conforms to the corresponding protocols.
The Interactor (the mock) will return a list of displayed objects, and the VC (again, mock) will raise some flag when the presenter will call its reload Data method.
Now, connect those mocks to the presenter, call on View Did Load method, and check to see if the presenter contains the display objects you put in the interactor, and if the flag of the reload Data is raised in the view controller.
It’s no coincidence I ended up with testing. If you keep the principles of the architecture, you’ll have a super flexible code, which will help you both working in a team, and fully test your app.
Also, the suggested architecture is not mandatory, and surely not the optimized one. There are no rules, it’s just a matter of how long you wanna invest in order to make your code flexible for testing.