iOS Architecture Patterns : Demystifying MVC, MVP, MVVM and VIPER
Feeling weird while doing MVC in iOS? Have doubts about switching to MVVM? Heard about VIPER, but not sure if it worth it? Keep reading, and you will find answers to questions above, if you don’t — feel free to complain in comments.
You are about to structure your knowledge about architectural patterns in iOS environment. We’ll briefly review some popular ones and compare them in theory and practice going over a few tiny examples. Follow links if you need more details about any particular one.
Mastering design patterns might be addictive, so beware: you might end up asking yourself more questions now than before reading this article, like these:
Who supposed to own networking request: a Model or a Controller?
How do I pass a Model into a View Model of a new View?
Why care about choosing the architecture?
Because if you don’t, one day, debugging a huge class with dozens different things, you’ll find yourself being unable to find and fix any bugs in your class.”. Naturally, it is hard to keep this class in mind as whole entity, thus, you’ll always be missing some important details. If you are already in this situation with your application, it is very likely that:
- This class is the UIViewController subclass.
- Your data stored directly in the UIViewController
- Your UIViews do almost nothing
- The Model is a dumb data structure
- Your Unit Tests cover nothing
Let’s define features of a good architecture:
- Balanced distribution of responsibilities among entities with strict roles.
- Testability usually comes from the first feature (and don’t worry: it is easy with appropriate architecture).
- Ease of use and a low maintenance cost.
Why Distribution?
Distribution keeps a fair load on our brain while we trying to figure out how things work. If you think the more you develop the better your brain will adapt to understanding complexity, then you are right. But this ability doesn’t scale linearly and reaches the cap very quickly. So the easiest way to defeat complexity is to divide responsibilities among multiple entities following the single responsibility principle.
Why Testability?
This is usually not a question for those who already felt gratitude to unit tests, which failed after adding new features or due to refactoring some intricacies of the class. This means the tests saved those developers from finding issues in runtime, which might happen when an app is on a user’s device and the fix takes a week to reach the user.
Why Ease of use?
This does not require an answer but it is worth mentioning that the best code is the code that has never been written. Therefore the less code you have, the less bugs you have. This means that desire to write less code should never be explained solely by laziness of a developer, and you should not favour a smarter solution closing your eyes to its maintenance cost.
MV(X) essentials
Nowadays we have many options when it comes to architecture design patterns:
First three of them assume putting the entities of the app into one of 3 categories:
- Models — responsible for the domain data or a data access layer which manipulates the data, think of ‘Person’ or ‘PersonDataProvider’ classes.
- Views — responsible for the presentation layer (GUI), for iOS environment think of everything starting with ‘UI’ prefix.
- Controller/Presenter/ViewModel — the glue or the mediator between the Model and the View, in general responsible for altering the Model by reacting to the user’s actions performed on the View and updating the View with changes from the Model.
MVC
How it used to be
Before discussing Apple’s vision of MVC let’s have a look on the traditional one.
In this case, the View is stateless. It is simply rendered by the Controller once the Modelis changed. Think of the web page completely reloaded once you press on the link to navigate somewhere else. Although it is possible to implement the traditional MVC in iOS application, it doesn’t make much sense due to the architectural problem — all three entities are tightly coupled, each entity knows about the other two. This dramatically reduces reusability of each of them — that is not what you want to have in your application. For this reason, we skip even trying to write a canonical MVC example.
Apple’s MVC
Expectation
The Controller is a mediator between the View and the Model so that they don’t know about each other. The least reusable is the Controller and this is usually fine for us, since we must have a place for all that tricky business logic that doesn’t fit into the Model.
In theory, it looks very straightforward, but you feel that something is wrong, right? You even heard people unabbreviating MVC as the Massive View Controller. Moreover, view controller offloading became an important topic for the iOS developers. Why does this happen if Apple just took the traditional MVC and improved it a bit?
Apple’s MVC
Reality
Cocoa MVC encourages you to write Massive View Controllers, because they are so involved in View’s life cycle that it’s hard to say they are separate. Although you still have ability to offload some of the business logic and data transformation to the Model, you don’t have much choice when it comes to offloading work to the Viewat most of times all the responsibility of the View is to send actions to the Controller. The view controller ends up being a delegate and a data source of everything, and is usually responsible for dispatching and cancelling the network requests and… you name it.
With all that said, it might seems that Cocoa MVC is a pretty bad pattern to choose. But let’s assess it in terms of features defined in the beginning of the article:
- Distribution — the View and the Model in fact separated, but the Viewand the Controller are tightly coupled.
- Testability — due to the bad distribution you’ll probably only test your Model.
- Ease of use — the least amount of code among others patterns. In addition everyone is familiar with it, thus, it’s easily maintained even by the unexperienced developers.
Cocoa MVC is the pattern of your choice if you are not ready to invest more time in your architecture, and you feel that something with higher maintenance cost is an overkill for your tiny pet project.
MVP
Cocoa MVC’s promises delivered
Doesn’t it look exactly like the Apple’s MVC? Yes, it does, and it’s name is MVP(Passive View variant). But wait a minute… Does this mean that Apple’s MVC is in fact a MVP? No, its not, because if you recall, there, the View is tightly coupled with the Controller, while the MVP’s mediator, Presenter has nothing to do with the life cycle of the view controller, and the View can be mocked easily, so there is no layout code in the Presenter at all, but it is responsible for updating the View with data and state.
n terms of the MVP, the UIViewController subclasses are in fact the Views and not the Presenters. This distinction provides superb testability, which comes at cost of the development speed, because you have to make manual data and event binding.
Let’s look on the features of the MVP:
- Distribution — we have the most of responsibilities divided between the Presenter and the Model, with the pretty dumb View (in the example above the Model is dumb as well).
- Testability — is excellent, we can test most of the business logic due to the dumb View.
- Easy of use — in our unrealistically simple example, the amount of code is doubled compared to the MVC, but at the same time, idea of the MVP is very clear.
MVVM
The latest and the greatest of the MV(X) kind
The MVVM is the newest of MV(X) kind thus, let’s hope it emerged taking into account problems MV(X) was facing previously.In theory the Model-View-ViewModel looks very good. The View and the Model are already familiar to us, but also the Mediator, represented as the View Model.It is pretty similar to the MVP:- the MVVM treats the view controller as the View
- There is no tight coupling between the View and the Model.
In addition, it does binding like the Supervising version of the MVP; however, this time not between the View and the Model, but between the View and the View Model.
So what is the View Model in the iOS reality? It is basically UIKit independent representation of your View and its state. The View Model invokes changes in the Model and updates itself with the updated Model, and since we have a binding between the View and the View Model, the first is updated accordingly.
Bindings
I briefly mentioned them in the MVP part, but let’s discuss them a bit here. Bindings come out of box for the OS X development, but we don’t have them in the iOS toolbox. Of course we have the KVO and notifications, but they aren’t as convenient as bindings.
So, provided we don’t want to write them ourselves, we have two options:
- One of the KVO based binding libraries like the RZDataBinding or the SwiftBond
- The full scale functional reactive programming beasts like ReactiveCocoa, RxSwift or PromiseKit.
In fact, nowadays, if you hear “MVVM” — you think ReactiveCocoa, and vice versa. Although it is possible to build the MVVM with the simple bindings, ReactiveCocoa (or siblings) will allow you to get most of the MVVM.
There is one bitter truth about reactive frameworks: the great power comes with the great responsibility. It’s really easy to mess up things when you go reactive. In other words, if do something wrong, you might spend a lot of time debugging the app, so just take a look at this call stack.
In our simple example, the FRF framework or even the KVO is an overkill, instead we’ll explicitly ask the View Model to update using showGreetingmethod and use the simple property for greetingDidChange callback function.
`import UIKit
struct Person { // Model let firstName: String let lastName: String } protocol GreetingViewModelProtocol: class { var greeting: String? { get } var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did change init(person: Person) func showGreeting() } class GreetingViewModel : GreetingViewModelProtocol { let person: Person var greeting: String? { didSet { self.greetingDidChange?(self) } } var greetingDidChange: ((GreetingViewModelProtocol) -> ())? required init(person: Person) { self.person = person } func showGreeting() { self.greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName } } class GreetingViewController : UIViewController { var viewModel: GreetingViewModelProtocol! { didSet { self.viewModel.greetingDidChange = { [unowned self] viewModel in self.greetingLabel.text = viewModel.greeting } } } let showGreetingButton = UIButton() let greetingLabel = UILabel() override func viewDidLoad() { super.viewDidLoad() self.showGreetingButton.addTarget(self.viewModel, action: "showGreeting", forControlEvents: .TouchUpInside) } // layout code goes here } // Assembling of MVVM let model = Person(firstName: "David", lastName: "Blaine") let viewModel = GreetingViewModel(person: model) let view = GreetingViewController()view.viewModel = viewModel`
And again back to our feature assessment:
- Distribution — it is not clear in our tiny example, but, in fact, the MVVM’s View has more responsibilities than the MVP’s View. Because the first one updates it’s state from the View Model by setting up bindings, when the second one just forwards all events to the Presenter and doesn’t update itself.
- Testability — the View Model knows nothing about the View, this allows us to test it easily. The View might be also tested, but since it is UIKit dependant you might want to skip it.
- Easy of use — its has the same amount of code as the MVP in our example, but in the real app where you’d have to forward all events from the Viewto the Presenter and to update the View manually, MVVM would be much skinnier if you used bindings.
The MVVM is very attractive, since it combines benefits of the aforementioned approaches, and, in addition, it doesn’t require extra code for the View updates due to the bindings on the View side. Nevertheless, testability is still on a good level.
Conclusion
We went though several architectural patterns, and I hope you have found some answers to what bothered you, but I have no doubt that you realised that there is no silver bullet so choosing architecture pattern is a matter of weighting tradeoffs in your particular situation.
Therefore, it is natural to have a mix of architectures in same app. For example: you’ve started with MVC, then you realised that one particular screen became too hard to maintain efficiently with the MVC and switched to the MVVM, but only for this particular screen. There is no need to refactor other screens for which the MVC actually does work fine, because both of architectures are easily compatible.
Make everything as simple as possible, but not simpler — Albert Einstein
Good information about the blog. Our apps are built with a consideration of the set rules and guidelines by iPhone platform.
ReplyDeleteHire iPhone Developer
MVP Development
I would like to thank you for the efforts you have made in writing this interesting and knowledgeable article. We have very experienced software developers for software, app, mobile development services in the UK. Hire our expert developer for custom MVP development in the UK.
ReplyDeleteminimum viable product software developer
Exploring iOS architecture patterns like MVC, MVP, MVVM, and VIPER is crucial for app development. Consider partnering with Frame X Lab for expert insights and implementation of these patterns in your projects. Frame X Lab's iOS development services ensure a seamless integration of the most suitable architecture, paving the way for scalable and efficient applications.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteThis comment has been removed by the author.
ReplyDelete