iOS Architecture Patterns
iOS architectural patterns
##iOS Architecture Patterns iOS architectural patterns
Demystifying MVC, MVP, MVVM and VIPER Demystifying MVC, MVP, MVVM and VIPER

Don’t miss the iOS Developer Roadmap for 2018!
UPD: Slides which I presented at NSLondon available here. UPD: The slides I presented at NSLondon can be found here.
Feeling weird while doing MVC in iOS? Have doubts about switching to MVVM? Heard about VIPER, but not sure if it’s worth it? Feeling weird using MVC in iOS? Have questions about switching to MVVM? Have heard of Viper, but not sure if it’s worth it?
Keep reading, and you will find answers to questions above, if you don’t feel free to complain in comments. Read on and you’ll find the answers to the questions above, and if you don’t – feel free to complain in the 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. You will build your knowledge about architectural patterns in the iOS environment. We will briefly review some popular examples and compare them in theory and practice. If you need detailed information about any one please follow the links.
Mastering design patterns might be addictive, so beware: you might end up asking yourself more questions now than before reading this article, like these: Mastering design patterns can be addictive, so be warned: you may be asking yourself more questions now than before reading this article, such as:
Who supposed to own networking request: a Model or a Controller? Who should own network requests: model or controller?
How do I pass a Model into a View Model of a new View? How do I pass a model into the view model of a new view?
Who creates a new VIPER module: Router or Presenter? Who created a new Viper module: the router or the demo program?

Why care about choosing the architecture?
Why should you care about choosing an architecture?
Because if you don’t, one day, debugging a huge class with dozens of 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: Because if you don’t do this, one day, when debugging a large class with many different things, you will find yourself unable to find and fix any bugs in the class. "Of course, it’s hard to remember the class as a whole, so you’re always missing some important details. If your application is already in this situation, it is likely that:
-
This class is the UIViewController subclass. This class is a UIViewController subclass.
-
Your data stored directly in the UIViewController Your data is stored directly in the UIViewController
-
Your UIViews do almost nothing Your uiview does almost nothing
-
The Model is a dumb data structure The model is a dumb data structure
-
Your Unit Tests cover nothing Your unit tests don’t cover anything
And this can happen, even despite the fact that you are following Apple’s guidelines and implementing Apple’s MVC pattern, so don’t feel bad. There is something wrong with the Apple’s MVC, but we’ll get back to it later. This can happen even if you follow Apple’s guidelines and implement Apple’s MVC pattern, so don’t feel bad. Apple’s MVC has some issues, but we’ll come back to it later.
Let’s define features of a good architecture: Let us define the characteristics of a good architecture:
-
Balanced distribution of responsibilities among entities with strict roles. 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). Testability usually comes from the first feature (don’t worry: it’s easy with the appropriate architecture).
-
Ease of use and a low maintenance cost. Easy to use and low maintenance cost.
Why Distribution?
Why distributed?
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. Distribution holds a considerable load on our brains as we try to figure out how things work. If you think that the more you develop, the better your brain will become adapted to understanding complexity, you’re right. But this ability does not grow linearly and will quickly reach an upper limit. Therefore, the simplest way to overcome complexity is to divide responsibility among multiple entities following the single responsibility principle.
Why Testability?
Testability reasons?
This is usually not a question for those who already felt grateful 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. This is usually not a problem for those who are already grateful for unit tests that fail after a new feature is added, or because some intricacy of the class is refactored. This means these tests allow developers to avoid discovering issues at runtime, which can happen when the app is on a user’s device and the fix takes a week to reach the user.
Why Ease of use?
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 favor a smarter solution closing your eyes to its maintenance cost. This doesn’t require an answer, but it’s worth mentioning that the best code is code that’s never been written. Therefore, less code means fewer bugs. This means that the desire to write less code shouldn’t be explained solely by developer laziness, and you shouldn’t turn a blind eye to maintenance costs in favor of smarter solutions.
MV(X) essentials
Necessities for MV (X)
Nowadays we have many options when it comes to architecture design patterns: Now, when it comes to architectural design patterns, we have many options:
*MVC *MVP *MVVM *VIPER
First three of them assume putting the entities of the app into one of 3 categories: The first three assumptions are to divide the application entities into three categories:
-
Models — Responsible for the domain data or a data access layer which manipulates the data, think of ‘Person’ or ‘PersonDataProvider’ classes. Model - The data access layer responsible for domain data or operational data, consider the “Person” or “PersonDataProvider” classes.
-
Views — Responsible for the presentation layer (GUI), for iOS environment think of everything starting with ‘UI’ prefix. View - Responsible for the presentation layer (GUI), for iOS environments, consider everything starting with “UI”.
-
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. Controller/Presenter/ViewModel—The glue or intermediary between the model and the view, typically responsible for changing the model in response to user actions on the view and updating the view with changes from the model.
Having entities divided allows us to: Entity splitting allows us to:
-
understand them better (as we already know) Understand them better (as we already know)
-
reuse them (mostly applicable to the View and the Model) Reuse them (mainly applies to views and models)
-
test them independently independent testing
Let’s start with MV(X) patterns and get back to VIPER later. Let’s start with MV(X) mode and return to VIPER later.
###MVC
How it used to be
how was it in the past
Before discussing Apple’s vision of MVC let’s have a look on the traditional one.
Before discussing Apple’s MVC vision, let’s take a look at traditional MVC.
TraditionalMVC
In this case, the View is stateless. It is simply rendered by the Controller once the Model is 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. In this case, the view is stateless. Once the model is changed, the controller simply renders it. When you press a link to navigate elsewhere, consider reloading the web page entirely. While it is possible to implement traditional MVC in an iOS app, this doesn’t make much sense due to architectural issues - all three entities are tightly coupled, and each entity knows about the other two. This greatly reduces their reusability - not something you want to have in your application. For this reason, we even skipped writing canonical MVC examples.
Traditional MVC doesn’t seems to be applicable to modern iOS development. Traditional MVC doesn’t seem to work for modern iOS development.
Apple’s MVC
Apple MVC
Expectation
Expectation
CocoaMVC
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. The controller is an intermediary between the view and the model, so they don’t know about each other. The least reusable thing is the controller, which is usually no problem for us because we have to provide a place for all the complex 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? In theory, this seems simple, but you feel like something is off, right? You’ve even heard people abbreviate MVC for Large View Controller. In addition, view controller offloading has become an important topic for iOS developers. Why would this happen if Apple just took traditional MVC and made some improvements to it?
Apple’s MVC
Apple MVC
Reality
reality
Realistic Cocoa MVCCocoa 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 View, at 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.
Cocoa MVC encourages you to write a large number of view controllers, because they are so important in the life cycle of the view that it is difficult to say that they are independent. Although you still have the ability to sell some business logic and data transformations to the model, you don’t have many options when it comes to offloading the work to the view; most of the time all the responsibility of the view is to send actions to the controller. The view controller is ultimately a delegate and the data source for everything, and is generally responsible for dispatching and canceling network requests, and… you name it.
How many times have you seen code like this: How many times have you seen code like this:
var userCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCell
userCell.configureWithUser(user)
The cell, which is the View configured directly with the Model, so MVC guidelines are violated, but this happens all the time, and usually people don’t feel it is wrong. If you strictly follow the MVC, then you supposed to configure the cell from the controller, and don’t pass the Model into the View, and this will increase the size of your Controller even more. The cell is a view configured directly with the model, thus violating MVC guidelines, but this happens so often that people usually don’t feel it is wrong. If you strictly follow MVC, you should configure the cell from the controller instead of passing the model to the view, which will further increase the size of the controller.
Cocoa MVC is reasonably unabbreviated as the Massive View Controller. Cocoa MVC is reasonably simplified into large view controllers.
The problem might not be evident until it comes to the Unit Testing (hopefully, it does in your project). Since your view controller is tightly coupled with the view, it becomes difficult to test because you have to be very creative in mocking views and their life cycle, while writing the view controller’s code in such a way, that your business logic is separated as much as possible from the view layout code. This problem may not be obvious before unit testing (hopefully it is in your project). Because your view controller is tightly coupled to views, it is difficult to test because when mocking you have to be very creative with views and their lifecycle, and in writing the view controller code in such a way that your business logic is separated as much as possible from the view layout code.
Let’s have a look on the simple playground example: Let’s look at a simple playground example:
UPD: See updated code examples by Wasin Thonkaew UPD: See Wasin Thonkaew’s updated code example
import UIKit
struct Person { // Model
let firstName: String
let lastName: String
}
class GreetingViewController : UIViewController { // View + Controller
var person: Person!
let showGreetingButton = UIButton()
let greetingLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
}
func didTapButton(button: UIButton) {
let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
self.greetingLabel.text = greeting
}
// layout code goes here
}
// Assembling of MVC
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
view.person = model;
MVC example
MVC assembling can be performed in the presenting view controller MVC assembly can be performed in the presenting view controller
This doesn’t seem very testable, right? We can move generation of greeting into the new GreetingModel class and test it separately, but we can’t test any presentation logic (although there is not much of such logic in the example above) inside the GreetingViewController without calling the UIView related methods directly (viewDidLoad, didTapButton) which might cause loading all views, and this is bad for the unit testing. This doesn’t look very feasible, right? We could substitue Greetings into the new GreetingModel class and test it individually, but we wouldn’t be able to test any presentation logic (although there isn’t much such logic in the example above) in the GreetingViewController without directly calling the UIView related method (viewDidLoad didTapButton) which might cause all views to be loaded, which is not conducive to unit testing.
In fact, loading and testing UIViews on one simulator (e.g. iPhone 4S) doesn’t guarantee that it would work fine on the other devices (e.g. iPad), so I’d recommend to remove “Host Application” from your Unit Test target configuration and run your tests without your application running on simulator. In fact, loading and testing a UIView on one simulator (like an iPhone 4S) does not guarantee that it will work correctly on other devices (like an iPad), so I recommend removing “Host Application” from the unit test target configuration and running the tests with the application running on the simulator.
The interactions between the View and the Controller aren’t really testable with Unit Tests The interaction between the view and the controller cannot actually be tested via unit tests
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: To sum up, choosing Cocoa MVC seems to be a very bad model. But let’s evaluate it from the characteristics defined at the beginning of the article:
-
**Distribution **—the View and the Model are in fact separated, but the View and the Controller are tightly coupled. Distribution - Views and models are effectively separate, but views and controllers are tightly coupled.
-
Testability — due to the bad distribution you’ll probably only test your Model. Testability - You may only test your model due to poor distribution.
-
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. Ease of use - Minimal amount of code among other modes. Furthermore, everyone is familiar with it, so it is easy to maintain even for inexperienced 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. If you are not ready to invest more time in the architecture and you feel that something with higher maintenance cost is redundant for your small pet project, then you can choose Cocoa MVC pattern.
Cocoa MVC is the best architectural pattern in terms of the speed of the development. In terms of development speed, Cocoa MVC is the best architectural pattern.
###MVP
Cocoa MVC’s promises delivered
The promise of Cocoa MVC has been delivered
Passive View variant of MVP
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. Doesn’t it look like Apple’s MVC? Yes, it is, and its name is MVP (Passive View Variant). But wait, does this mean that Apple’s MVC is actually MVP? No, it’s not, because if you remember, there, the view is tightly coupled to the controller, and the MVP’s mediator, the presenter, has nothing to do with the lifecycle of the view controller, the view can be easily mocked, so there is no layout code in the presenter, but it is responsible for updating the view with data and state.
What if I told you, the UIViewController is the View. If I tell you that UIViewController is a view.
In 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, as you can see from the example: As far as MVP is concerned, UIViewController subclasses are actually views rather than presenters. This distinction provides excellent testability, but it comes at the cost of development speed because you have to do manual data and event binding, as you can see from the example:
import UIKit
struct Person { // Model
let firstName: String
let lastName: String
}
protocol GreetingView: class {
func setGreeting(greeting: String)
}
protocol GreetingViewPresenter {
init(view: GreetingView, person: Person)
func showGreeting()
}
class GreetingPresenter : GreetingViewPresenter {
unowned let view: GreetingView
let person: Person
required init(view: GreetingView, person: Person) {
self.view = view
self.person = person
}
func showGreeting() {
let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
self.view.setGreeting(greeting)
}
}
class GreetingViewController : UIViewController, GreetingView {
var presenter: GreetingViewPresenter!
let showGreetingButton = UIButton()
let greetingLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
}
func didTapButton(button: UIButton) {
self.presenter.showGreeting()
}
func setGreeting(greeting: String) {
self.greetingLabel.text = greeting
}
// layout code goes here
}
// Assembling of MVP
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
let presenter = GreetingPresenter(view: view, person: model)
view.presenter = presenter
MVP example
Important note regarding assembly
Important notes about assembly
The MVP is the first pattern that reveals the assembly problem which happens due to having three actually separate layers. Since we don’t want the View to know about the Model, it is not right to perform assembly in presenting view controller (which is the View), thus we have to do it somewhere else. For example, we can make the app-wide Router service which will be responsible for performing assembly and the View-to-View presentation. This issue arises and has to be addressed not only in the MVP but also in all the following patterns. MVP is the first pattern to reveal assembly issues due to having three actually independent layers. Since we don’t want the view to know about the model, it is incorrect to perform the assembly when the view controller (i.e. the view) is presented, so we have to do it elsewhere. For example, we can make an application-wide router service which will be responsible for performing assembly and view-to-view presentation. This problem does not only appear in MVP, but must be solved in all following patterns.
Let’s look on the features of the MVP: Let’s take a look at the characteristics of 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). Distribution - We distributed most of the responsibilities between the presenter and the model, and used pretty stupid views (in the example above, the model was also stupid).
-
Testability—is excellent, we can test most of the business logic due to the dumb View. Testability - Very good, we can test most of the business logic thanks to dumb views.
-
Easy of use— In our unrealistically simple example, the amount of code is doubled compared to the MVC, but at the same time, the idea of the MVP is very clear. Ease of use - In our unrealistically simple example, the amount of code is doubled compared to MVC, but at the same time, the concept of MVP is very clear.
MVP in iOS means superb testability and a lot of code. MVP in iOS means great testability and lots of code.
###MVP most valuable player
With Bindings and Hooters
with rope and hat
There is the other flavor of the MVP—the Supervising Controller MVP. This variant includes direct binding of the View and the Model while the Presenter (The Supervising Controller) still handles actions from the View and is capable of changing the View. There is another flavor of MVP–supervisory player MVP. This variant includes direct binding of the view and model, while the presenter (monitoring controller) still handles operations from the view and is able to change the view.
Supervising Presenter variant of the MVP
But as we have already learned before, vague responsibility separation is bad, as well as tight coupling of the View and the Model. That is similar to how things work in Cocoa desktop development. But as we’ve learned before, vague separation of responsibilities is bad, and so is tight coupling of the view and model. This is similar to how it works in Cocoa desktop development.
Same as with the traditional MVC, I don’t see a point in writing an example for the flawed architecture. As with traditional MVC, I don’t think it’s necessary to write examples for a flawed architecture.
###MVVM
The latest and the greatest of the MV(X) kind The Latest and Greatest MV(X) CategoryThe MVVM is the newest of MV(X) kind thus, let’s hope it emerged taking into account problems MV(X) was facing previously. MVVM is the latest MV(X) type, so let’s hope it comes out considering the issues MV(X) faced before.
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. In theory, model-view-viewmodel looks very good. Views and models are already familiar to us, and so are mediations, represented as view models.

It is pretty similar to the MVP: This is very similar to MVP:
-
the MVVM treats the view controller as the View MVVM treats view controllers as views
-
There is no tight coupling between the View and the Model 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. Furthermore, it does bind like the regulatory version of MVP; however, this time it’s 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. What is a view model in iOS? It is basically UIKit’s independent representation of a view and its state. The view model calls the 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 model will be updated accordingly.
Bindings
binding
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. I mentioned them briefly in the MVP section, but let’s discuss them here. Bindings come out of the box for OS X development, but we don’t have them in the iOS toolbox. Of course, we have KVO and notifications, but they are not as convenient as binding.
So, provided we don’t want to write them ourselves, we have two options: So, if we don’t want to write it ourselves, we have two options:
-
One of the KVO based binding libraries like the RZDataBinding or the SwiftBond One of the KVO based binding libraries such as RZDataBinding or SwiftBond
-
The full scale functional reactive programming beasts like ReactiveCocoa, RxSwift or PromiseKit. Full-featured reactive programming tools like ReactiveCocoa, RxSwift or promise eKit.
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. In fact, nowadays, if you hear “MVVM”, you think of ReactiveCocoa, and vice versa. While it is possible to build MVVM with simple bindings, ReactiveCocoa (or a sibling) will allow you to get most of 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 you do something wrong, you might spend a lot of time debugging the app, so just take a look at this call stack. There’s a painful truth about reactive frameworks: With great power comes great responsibility. It’s easy to mess up when you overreact. In other words, if you do something wrong, you may spend a lot of time debugging your application, so just look at this call stack.
Reactive Debugging
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 showGreeting method and use the simple property for greetingDidChange callback function. In our simple example, the FRF framework or even KVO is redundant, instead we will explicitly ask the view model to update using the showGreeting method and a simple property using the 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
MVVM example
And again back to our feature assessment: Back to our feature evaluation:
-
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. Distribution - It’s not clear in our small example, but in fact, MVVM’s view has more responsibilities than MVP’s view. Because the first one updates the state in the view model by setting the binding, while the second one just forwards all events to the presenter without updating 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. Testability – The view model knows nothing about the view, which allows us to easily test it. Views may also be tested, but since it relies on UIKit you may 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 View to the Presenter and to update the View manually, MVVM would be much skinnier if you used bindings. Easy to use - it has the same amount of code as MVP in our example, but in a real application you would have to forward all events from the view to the presenter and update the view manually, MVVM would be thinner if bindings were used.> 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. MVVM is very attractive because it combines the advantages of the above methods and, due to the binding on the view side, it does not require additional code to update the view. Still, testability is at a good level.
VIPER
LEGO building experience transferred into the iOS app design LEGO building experience transferred to iOS app design
VIPER is our last candidate, which is particularly interesting because it doesn’t come from the MV(X) category. VIPER is our last candidate, which is very interesting because it is not of class MV(X).
By now, you must agree that the granularity in responsibilities is very good. VIPER makes another iteration on the idea of separating responsibilities, and this time we have five layers. By now you have to agree that the granularity in the responsibilities is pretty good. VIPER has another iteration of the concept of separation of responsibilities, this time we have 5 layers.
VIPER
-
Interactor — contains business logic related to the data (Entities) or networking, like creating new instances of entities or fetching them from the server. For those purposes you’ll use some Services and Managers which are not considered as a part of VIPER module but rather an external dependency. Interactor—Contains business logic related to data (entities) or network, such as creating new instances of entities or retrieving them from the server. For these purposes you will use some services and managers that are not considered part of the VIPER module but an external dependency.
-
Presenter means—contains the UI related (but UIKit independent) business logic, invokes methods on the Interactor. Presenter - Contains business logic related to UI (but independent of UIKit), calling methods on the interactor.
-
Entities means—your plain data objects, not the data access layer, because that is a responsibility of the Interactor. Entities - your normal data objects, not the data access layer as that is the responsibility of the interactor.
-
Router—responsible for the segues between the VIPER modules. Router - Responsible for segue between Viper modules.
Basically, VIPER module can be a one screen or the whole user story of your application — think of authentication, which can be one screen or several related ones. How small are your “LEGO” blocks supposed to be? — It’s up to you. Basically, a VIPER module can be one screen or the entire user story of the application - think about authentication, it can be one screen or several related screens. How small should your “LEGO” bricks be? - It’s up to you.
If we compare it with the MV(X) kind, we’ll see a few differences of the distribution of responsibilities: If we compare this to the MV(X) type we will see some differences in the distribution of responsibilities:
-
Model (data interaction) logic shifted into the Interactor with the Entities as dumb data structures. Model (data interaction) logic is converted into dumb data structures that interact with entities.
-
Only the UI representation duties of the Controller/Presenter/ViewModel moved into the Presenter, but not the data altering capabilities. Only the UI presentation functionality of the controller/presenter/viewmodel is transferred to the presentation, not the data modification functionality.
-
VIPER is the first pattern which explicitly addresses navigation responsibility, which is supposed to be resolved by the Router. VIPER is the first pattern to explicitly handle navigation responsibilities, which should be handled by the router.
Proper way of doing routing is a challenge for the iOS applications, the MV(X) patterns simply don’t address this issue. Correct routing is a challenge for iOS applications, and the MV(X) pattern simply cannot solve this problem.
The example doesn’t cover routing or interaction between modules, as those topics are not covered by the MV(X) patterns at all. This example does not involve routing or interactions between modules, as the MV(X) pattern does not cover these topics at all.
import UIKit
struct Person { // Entity (usually more complex e.g. NSManagedObject)
let firstName: String
let lastName: String
}
struct GreetingData { // Transport data structure (not Entity)
let greeting: String
let subject: String
}
protocol GreetingProvider {
func provideGreetingData()
}
protocol GreetingOutput: class {
func receiveGreetingData(greetingData: GreetingData)
}
class GreetingInteractor : GreetingProvider {
weak var output: GreetingOutput!
func provideGreetingData() {
let person = Person(firstName: "David", lastName: "Blaine") // usually comes from data access layer
let subject = person.firstName + " " + person.lastName
let greeting = GreetingData(greeting: "Hello", subject: subject)
self.output.receiveGreetingData(greeting)
}
}
protocol GreetingViewEventHandler {
func didTapShowGreetingButton()
}
protocol GreetingView: class {
func setGreeting(greeting: String)
}
class GreetingPresenter : GreetingOutput, GreetingViewEventHandler {
weak var view: GreetingView!
var greetingProvider: GreetingProvider!
func didTapShowGreetingButton() {
self.greetingProvider.provideGreetingData()
}
func receiveGreetingData(greetingData: GreetingData) {
let greeting = greetingData.greeting + " " + greetingData.subject
self.view.setGreeting(greeting)
}
}
class GreetingViewController : UIViewController, GreetingView {
var eventHandler: GreetingViewEventHandler!
let showGreetingButton = UIButton()
let greetingLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
}
func didTapButton(button: UIButton) {
self.eventHandler.didTapShowGreetingButton()
}
func setGreeting(greeting: String) {
self.greetingLabel.text = greeting
}
// layout code goes here
}
// Assembling of VIPER module, without Router
let view = GreetingViewController()
let presenter = GreetingPresenter()
let interactor = GreetingInteractor()
view.eventHandler = presenter
presenter.view = view
presenter.greetingProvider = interactor
interactor.output = presenter
Yet again, back to the features: Back to features again:
-
Distribution — Undoubtedly, VIPER is a champion in distribution of responsibilities. Distribution - Without a doubt, the Viper is a champion at distributing responsibility.
-
Testability —no surprises here, better distribution—better testability. Testability - no surprises here, better distribution - better testability.
-
Easy of use — Finally, two above come in cost of maintainability as you already guessed. You have to write huge amount of interface for classes with very small responsibilities. Ease of use – Finally, as you may have guessed, the two items mentioned above are maintainability costs. You have to write a lot of interfaces for classes with very small responsibilities.
So what about LEGO?
What about Lego?While using VIPER, you might feel like building The Empire State Building from LEGO blocks, and that is a signal that you have a problem. Maybe, it’s too early to adopt VIPER for your application and you should consider something simpler. Some people ignore this and continue shooting out of cannon into sparrows. I assume they believe that their apps will benefit from VIPER at least in the future, even if now the maintenance cost is unreasonably high. If you believe the same, then I’d recommend you to try Generamba — a tool for generating VIPER skeletons. Although for me personally it feels like using an automated targeting system for cannon instead of simply taking a sling shot. When using VIPER, you may feel like you are building the Empire State Building out of Legos, which is a sign that you have a problem. Maybe, it’s too early to adopt VIPER for your application and you should consider some simpler methods. Some people ignored this and continued to shoot at the sparrows with their cannons. I assume they believe their application will benefit from VIPER at least in the future, even if the maintenance costs are unreasonably high now. If you think so, then I suggest you try Generamba - a tool to generate the skeleton of a venomous snake. Although to me personally it feels like shooting with an auto-aim system rather than a simple slingshot.
###Conclusion Conclusion
We went through several architectural patterns, and I hope you have found some answers to what bothered you, but I have no doubt that you realized that there is no silver bullet so choosing architecture pattern is a matter of weighting tradeoffs in your particular situation. We’ve discussed several architectural patterns and I hope you’ve found some answers to the questions that have been bothering you, but I’m sure you’ve realized that there’s no magic bullet, so choosing an architectural pattern is a matter of weighing the pros and cons in your specific situation.
Therefore, it is natural to have a mix of architectures in the same app. For example: you’ve started with MVC, then you realized 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. Therefore, it is natural to mix architectures in the same application. For example: you start with MVC, then you realize that it’s difficult to maintain a specific screen efficiently using MVC, so switch to MVVM, but only for this specific screen. There is no need to refactor other screens where MVC actually works well, as both architectures are easily compatible.
Make everything as simple as possible, but not simpler—Albert Einstein Make everything as simple as possible, but not as simple as possible - Albert Einstein
Thank you for reading! If you liked this article, please clap so other people can read it too :) Thanks for reading! If you liked this post, please clap so others can read it too.
Follow me on Twitter for more iOS design and patterns. Follow me on Twitter for more iOS design and patterns.
读完之后,下一步看什么
如果还想继续了解,可以从下面几个方向接着读。