iOS VIPER: The most suitable design patterns that come together
Resuming our iOS VIPER series, it's time to talk about the most important design patterns that fit a VIPER scene. Since VIPER is a complex architecture, there are multiple ways to create and bind its layers, establish a relationship between different sides and make your code more legible. I might be forgetting some other patterns that could be used, if I am, please paste a comment with some new suggestions. Said that, let's start.
Factory
As you may already know, Factory is a design pattern that is applied to customize creation of a huge structure of related objects. Basically, instead of initializing multiple objects and injecting them into each other, which may cause a lot messy and confusion, the Factory pattern delegates all this responsability to an extra type.
As you can see, our SceneFactory
consists of a single method, which shall receive or not some inputs from our previous scene and creates a new one layer by layer by instantiating, injecting properties and assigning delegates. As a result, a whole new view controller is returned to be pushed into our screen stack.
But why it's the view controller who's being returned? Could it be Presenter our Interactor?
Since UI is the core of our app, it's very important to return an object that is related to our navigation and transition between our scenes, that are view controller types under the hood.
Of course you could return something else like Router by just establishing an interface between their view controllers and then triggering a change of context, but since this operation is pure UI, returning the view controller itself is cleaner.
Adapter
Adapter was designed to convert a data model into another type by injecting this input instance via init. Basically we apply that when we want to convert our Entities
, which originate from the Interactor
use cases into ViewModels
that shall be consumed by our Presenter
. You should remember Interactor
and its entities are only related to business logic, no UI at all. These entities are adapted to some UI friendly format, ready to fill the interface. We shall declare an initialization method receiving our input type, ContactEntity
and consume this data to create our new output object
Observer
Observer is a design pattern where we have a class that shall emit some events, which we call Observable
and these events are handled by one or more Observers
. The most standard way of implementing that is via NotificationCenters
but the idea of that is to broadcast these events to multiple scenes in the app. Let's say the user turned a flag on in a scene and that should reflect some behavior or layout of a previous scene that is too complex to pass the information backwards. The most suitable solution for that is creating an Observable-Observer relationship between those scenes. As these events come from use cases, the special layer for keeping this relationship is the Interactor
:
After the Observer Interactor
receives an event, that may reflect changes in its presentation layer.
Facade
In the VIPER structure itself we can see Presenter
as some kind of Facade since it works like a hub to all the other layers. It handles inputs from View
and use case outputs from Interactor
and decides which operation should go next: a screen update, some new use case or maybe a change of scene. The point of Facade is to abstract some logic and that's the idea of Presenter
in our given architecture.
Delegation
The delegation pattern defines a protocol with some methods to be implemented by some class as a continuation to the pipeline of an origin class. We see that a lot in UIKit components(UITableView, UISearchBar, UITextField, etc). The shortest description of that is about handling events. We have delegates for our Presenter
and Interactor
layers, respectively some methods to tell the View
some presentation logic was executed and to tell Presenter
it received an output from some use case. Presenter
implements a delegate of Interactor
and View
implements a delegate of Presenter
. However, through my nomenclature I call the delegates from VIPER as outputs:
Dependency Injection
Dependency Injection is simply abstracting some part of the logic within another type and injecting this instance into the original class. That's exactly what we do when we split the scene's tasks into different layers, each upper layer abstracts some implementation and is injected into another layer.
Of course there are multiple ways of doing that, but in VIPER:
- Interactor is injected into Presenter via
init
- Presenter is injected into View via
init
- View is injected into Presenter via assignment
- Presenter is injected into Interactor via assignment
- Router is injected into Presenter via
init
- View is injected into Router via assignment
For short, all the layers should be injected via init
, except for the weak properties that should be injected via assignment. Check that in our Factory
section for implementation details.
Dependency Inversion
This pattern comes together with Dependency Injection but works for testing purposes. As we aim isolation between different logics, when we don't want to rely on a logic of an injected layer, instead of injecting the concrete type itself, we inject a mock that implements the same protocol in order to only test the logic from the suit under test:
This way, if the Interactor
implementation has some issues, they won't interfere in the expected result from the method we are testing from Presenter. Check our mocking class:
As it implements the ContactListInteractorInput
protocol it perfectly fits Presenter
when we want to test it.
Coordinator
I know it may be worthless talking about it again, but Coordinator
is a design pattern where we abstract navigation logic from a scene into a single layer. In VIPER this is the same as Router
. Of course there may be some differences from the traditional Coordinator that may work to implement navigation to an entire application and our Router
should be only the exit point of a scene but the purpose is the same, just more restricted:
As I talked before, it's essential for a Router/Coordinator to have access to the main navigationController.
Conclusion
As we saw in this article, there are multiple design patterns that fit a VIPER project in a scene. Some of them like Facade, Coordinator and Dependency Injection are essential for VIPER to work properly. Maybe you already knew them but didn't identify by name. They are great solutions to make your code cleaner, testable and legible. I hope you enjoyed ;)