Clean Swift, also known as VIP (View-Interactor-Presenter) architecture, is an iOS application design pattern that Raymond Law introduced it in 2014 as an alternative to the popular Model-View-Controller (MVC) pattern. VIP architecture’s primary goal is to make the codebase more modular, testable, and scalable.
When should you use VIP architecture?
VIP architecture is well suited for large, complex iOS applications that necessitate the collaboration of multiple developers. It’s also useful for applications that need a lot of test coverage and upkeep.
VIP Components
VIP architecture is made up of five major components:
View
The View layer is in charge of displaying data to the user as well as handling user interactions. It is a passive layer that is unaware of the underlying business logic.
Interactor
The Interactor layer contains the application’s business logic. It receives requests from the Presenter layer and executes the required operations. Data retrieval, processing, and validation are all handled by the Interactor layer.
Presenter
The Presenter layer acts as a go-between for the View and the Interactor. It takes user input from the View and sends it to the Interactor for processing. The Presenter layer is also in charge of formatting data from the Interactor and passing it to the View for display.
Router
The Router layer is in charge of application navigation. Based on user actions and business logic, it determines which screen to display.
Entities
Entities represent the application’s data models. They include the properties and methods necessary to represent business objects. Swift structs or classes are commonly used to implement Entities.
Using a Configurator
The Configurator component in the VIP architecture pattern is in charge of configuring components for a given view controller. The Configurator function is typically implemented as a separate class or module, with the goal of decoupling the VIP component setup from the view controller itself.
The Configurator creates and configures the view controller’s VIP components, such as the Interactor, Presenter, and Router. It also creates the links between the components and the view controller and returns the view controller with the components attached once the components have been configured.
The Configurator allows to encapsulate and segregate the components from the view controller . Additionally, because the VIP components can be tested independently of the view controller, it offers a cleaner, more modular codebase that is simpler to test.
Example code
Let’s see an schematic example of the application of VIP architecture with Configurator. As one of the main drawbacks of the VIP architecture is the boilerplate code need for fill all the classes in a scene, we will use a template (you can find many Xcode templates for VIP over Internet).
View+ViewController
protocol ExampleViewDelegate: class {}
final class ExampleView: UIView {
weak var delegate: ExampleViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
configureUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configureUI() {}
}
import UIKit
protocol ExampleViewControllerInput: class {}
protocol ExampleViewControllerOutput: class {}
final class ExampleViewController: UIViewController {
var interactor: ExampleInteractorInput?
var router: ExampleRouterDelegate?
private let sceneView: ExampleView
init(sceneView: ExampleView) {
self.sceneView = sceneView
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
self.view = sceneView
}
}
extension ExampleViewController: ExampleViewControllerInput {}
extension ExampleViewController: ExampleViewDelegate {}
Interactor
import Foundation
protocol ExampleInteractorOutput: class { }
typealias ExampleInteractorInput = ExampleViewControllerOutput
final class ExampleInteractor {
var presenter: ExamplePresenterInput?
}
extension ExampleInteractor: ExampleInteractorInput {}
Presenter
import UIKit
typealias ExamplePresenterInput = ExampleInteractorOutput
typealias ExamplePresenterOutput = ExampleViewControllerInput
final class ExamplePresenter {
weak var viewController: ExamplePresenterOutput?
}
extension ExamplePresenter: ExamplePresenterInput {}
Router
import UIKit
protocol ExampleRouterDelegate {}
final class ExampleRouter {
weak var viewController: UIViewController?
}
extension ExampleRouter: ExampleRouterDelegate {}
Configurator
import Foundation
final class ExampleConfigurator {
static func configure( _ viewController: ExampleViewController) -> ExampleViewController {
let interactor = ExampleInteractor()
let presenter = ExamplePresenter()
let router = ExampleRouter()
router.viewController = viewController
presenter.viewController = viewController
interactor.presenter = presenter
viewController.interactor = interactor
viewController.router = router
return viewController
}
}
Configurator code explained
In the ExampleConfigurator, we have a method (configure) that takes an instance of ExampleViewController as an argument and returns an instance of ExampleViewController, which will be ‘configured’. Inside this method, this is waht occurs:
- Instances of ExampleInteractor, ExamplePresenter, and ExampleRouter are created.
- The viewController properties of the router and the presenter are set to the viewController passed.
- The presenter property of the interactor instance is set to the presenter instance created at the beginning.
- The interactor and router instances are assigned to the interactor and router properties of the viewController.
- Finnally, the ‘configured’ viewController passed to the function is returned. So we have set up all the dependencies required for the ExampleViewController scene to work.
Protocols nomenclature
As you have seen, there are a lot of protocols that allows the flow of informaction. In this case, we have also use a Input/Output convention to define the inputs and outputs of each component. In order to accomplish that, we use typealias to ‘rename’ this protocols in each case.
Advantajes and drawbacks
As any other architecture, VIP has advantajes and drawbacks.
Advantajes
- Follows Clean Architecture’s principles.
- The flow of data is unidirectional.
- Uses Simple Responsibility Principle, which results in smaller methods.
- Uses protocols (interfaces) in order to make code more modular and reusable (so we can change one piece for another without impact on the rest of the application).
- It’s easy to maintain and debug.
- Unit tests can be easily added.
Drawbacks
- VIP have a lot of protocols and boilerplate code, which can be confusing. It’s advisable to use templates.
- For newbies could be overhelming, as at first sight seems over-engineered.
- Is not usually recommended in small applications.
- It must be taken into account that, although it is a small functionality to add, the amount of code to write is high.