Some time ago I stumbled upon the post about iOS architecture patterns with a quite provocative title “The only viable iOS architecture”. The answer to the question from the title is literally MVC. In a nutshell, MVC is the only viable and thus the best architecture for an iOS app.
The main idea of that post is that people simply do MVC in the wrong way. That ViewController is actually part of the presentation layer and the Model part represents the whole Domain Model, not just some data entity. In general, I agree with the ideas from that post, but I wouldn’t write this story if I agree with every statement from that post, would I?
I noticed that the author mostly didn’t touch one very important aspect of well-formed app architecture: covering the app business logic (BL) with unit tests (UT). As for me, it is one of the most significant factors for sensible app architecture. If one can not extract the app BT and implement UT with sufficient coverage, then such architecture simply sucks.
Moreover, if an app does not have UT, then it’s not feasible to prove the point above, and thus it is most probable that its architecture still sucks. You may reassure yourself that you can easily implement UT once you have a break from rush Sprints while your Product Owner is on vacation, or simply because you have the dedicated “Tests” target in the XCode project with some dummy template UT. But that’s not more than an illusion, believe me. I have a strong opinion, that if the unit tests are not implemented along with the features or soon after delivery, they won’t be implemented ever.
Taking this statement as an axiom, the app architecture must provide an ability to separate BL from UI presentation and make it testable. It’s pretty clear, that the UIViewController subclass is not the best candidate for this role due to its dependency on the lifecycle. This means that the class responsible for BL must lie in between UIViewController and Services, or in other words between View and Domain Model.
It worth to note, that by Services I mean the logic responsible for networking, communication with DB, sensors, Bluetooth, Keychain, 3rd party services, etc. In other words the shared part for multiple places, screens, in the app. While Business Logic part on the diagram corresponds to only one screen or screen component which is represented with a view controller. In the article about MVC mentioned in the beginning, the author combines BL and Domain Model parts together, while accepting that UIViewController is part of the presentation logic, i.e. View.
Now, when the need for the separation of the presentation and business logic is nailed down, let’s consider how these two parts will communicate with each other. And this is where the well-known architecture patterns come to the stage.
Model View Presenter
In MVP pattern Presenter and View are linked with each other via protocols. Presenter is injected with the instance of the View protocol and vice versa. View’s protocol must have a sufficient interface for presenting raw data in UI, while Presenter’s one for transmitting events received from user or system, like touches, gestures, shakes, etc. UIViewController subclass represents the View part here, while the Presenter class must not depend on UIKit (import UIKit sometimes is necessary though, to operate with data classes like UIImage for example).
On iOS, because of the way UIViewController lifecycle works, it must have a strong reference to the Presenter instance, while the last must have a weak one, to avoid retain cycles. This configuration reminds the Delegate pattern. In most cases, UIViewController may have the direct typed reference to the Presenter, but in some, the last could be injected into the first via the protocol as well. This may be useful if the same presentation is used with different business logic. Presenter’s link to UIViewController must be through a protocol in order to mock and cover it with UT. I would not be much specific on the Service part, but in order to test Presenter, it must be injected in with a protocol as well.
More details about MVP along with the basic example may be found here.
Model View ViewModel
In MVVM pattern the presentation and business parts communicate with each other using reactive bindings and they called View and ViewModel respectively. In iOS normally one would use ReactiveCocoa, RxSwift or modern Combine frameworks for reactive bindings, which are usually located in the ViewModel class and consumed by ViewController through a protocol as well. In part of communication with Services or Domain Model, there is no big difference with MVP, but one may prefer to use bindings or reactive events here as well. As in the previous pattern, the dependencies must be injected with the protocols in order to mock them in UT.
More details about MVVM along with the basic example may be found here.
The separate topic is routing. In iOS presenting a new screen modally or pushing to the navigation stack are manipulations with UIViewController subclasses. However, these operations may be part of the BL and be subject to cover with UT, e.g. if a particular event happens the screen must be dismissed. In this case, it makes sense to separate this part of the app logic into a class called Router. Therefore the pattern becomes MVP+R or MVVM+R. In some sources, you may find this part named as Coordinator and MVVP+C or MVVM+C respectively. While the Coordinator may have some additional logic except the routing, I prefer to treat them the same conceptually. The link between ViewModel and Router must be through a protocol, and the last must be responsible only for screens manipulations, all BL must be still concentrated in the first. And thus, Router is not the subject for UT, but ViewModel in the part of the communication with it.
The sample project with MVVM+R architecture pattern implementation may be found on my GitHub.
VIPER iOS architecture pattern is an extension of MVVM+R where ViewModel is divided into two pieces: Interactor and Presenter. The first is responsible for communication with Entity, i.e. Domain Model. The second prepares model classes to be presented in the View. To be honest, I’ve never used this pattern, because for me it appears too much distributed and complicated. MVVM+R separation of concerns was always sufficient for me.
In MVVM+R each module (screen) must be presented with a minimum of 3 classes: ViewController, ViewModel and Router. And there must be a place where all these parts are instantiated and linked to each other, the point of the module building. The most appropriate place for this is Router, because it is not coupled to the iOS UIViewController lifecycle, and must know about how the screen was presented in order to dismiss it properly. However, in some cases, it’s more convenient to move this part to a separate class called Builder, and this is what happens in RIBs, the architecture pattern from Uber. The ViewModel is renamed to Interactor, the rest of the parts remain the same. This pattern has some more interesting ideas and techniques Uber introduced, which one may read on the RIBs wiki. However, the most practically useful thing I found in the RIBs repository is the XCode template that helps one to avoid boilerplate coding when introducing a new RIBlet in the project. These templates may be easily adopted for MVVM+R classes as well. Kudos to Uber iOS engineers 👏.
A couple of words about unidirectional architecture patterns on iOS. If one looks at the scheme for the patterns above, they all have bidirectional connections between the components. This is not the case in Redux. This architecture pattern came to mobile from web development originally, React framework particularly. The most popular implementation of this concept for iOS development in the ReSwift framework. I will not go into detail much, because haven’t used this architecture in production apps. However, it is obvious that people coming to iOS from web development find this architecture pattern the most intuitive and familiar.
The best app architecture has been always a “holy war” topic, so now I’m more tending to the idea formalized by John Sundell in one of his last talk:
The best architecture is the one you and your team have created together to fit your project, by combining standard patterns and techniques with system design.