In this blog we are going to discuss about iOS architecture design patterns.we have various options when it comes to architecture design patterns:
- MVC
- MVVM
- VIPER
First, let’s start MVC pattern
Realistic Cocoa MVC
Cocoa MVC persuades you to write Massive View Controllers, since they are so concerned in View’s life cycle that it’s hard to state they are divided. While you still have ability to offload several 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, each 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 the whole thing, and is frequently responsible for dispatching and cancelling the network requests and… you name it.
var userCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCell userCell.configureWithUser(user)
The cell, which is the View organized directly with the Model, so MVC strategy are desecrated, but this occurs all the time, and generally people don’t feel it is wrong. If you strictly pursue the MVC, then you invented to configure the cell from the controller, and don’t pass the Model into the View, and this will raise the size of your Controller even more.
Cocoa MVC is logically unshortened as the Massive View Controller.
The problem may not be evident until it comes to the Unit Testing (hopefully, it does in your project). As your view controller is strongly coupled with the view, it becomes difficult to test because you have to be very creative in mocking views and their life cycle, whereas 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.
Let’s have a appear on the simple playground example:
MVC 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 assembling can be execute 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 within the GreetingViewController without calling the UIView related methods directly (viewDidLoad, didTapButton) which may cause loading all views, and this is bad for the unit testing.
In detail, loading and testing UIViews on one simulator (e.g. iPhone 4S) doesn’t assurance that it would work fine on the other devices (e.g. iPad), so I’d suggest to remove “Host Application” from your Unit Test target configuration and run your tests without your application running on simulator.
The connections between the View and the Controller aren’t really testable with Unit Tests
With everyone said, it may appear that Cocoa MVC is a pretty bad pattern to desire. But let’s assess it in terms of features defined in the starting of the blog:
Distribution — the View and the Model in fact divided, but the Viewand the Controller are tightly joined.
Testability — you’ll possibly only test your Model, due to the bad distribution.
Ease of use — the smallest amount of code among others patterns. In addition everyone is well-known with it, thus, it’s simply maintained even by the unexperienced developers.
Cocoa MVC is the outline of your choice if you are not set to use more time in your architecture, and you feel that something with higher protection cost is excess for your small project.
MVVM
The MVVM act the view controller as the View
There is no fixed combination between the View and the Model
Yet, this time not between the View and the Model, but between the View and the View Model.
Therefore what is the View Model in the iOS reality? It is essentially UIKit independent illustration of your View and its state. The View Model raise modifies in the Model and updates itself with the updated Model, and as we have a binding between the View and the View Model, the first is updated accordingly.
In our simple example, the FRF framework or even the KVO is overload, as a substitute we’ll openly ask the View Model to inform using showGreetingmethod and use the simple property for greetingDidChange callback function.
MVVM example
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
Our feature appraisal:
Distribution — in our small example it is not clear, but, in fact, the MVVM’s View has more responsibilities than the MVP’s View. Because the first one updates it’s position from the View Model by setting up bindings, when the second one just forwards every events to the Presenter and doesn’t modernize itself.
Testability — the View Model identifing nothing about the View, this permits us to test it simply. The View may be also tested, but while it is UIKit dependant you may want to skip it.
Ease of use — in the actual app where you’d have to promote all events from the Viewto the Presenter and to update the View manually; MVVM would be much thinner if you used bindings.
The MVVM is very striking, since it joins advantages of the abovementioned approaches, and, in addition, it doesn’t need extra code for the View updates due to the bindings on the View side. however, testability is still on a fine level.
VIPER
LEGO building skills shifted into the iOS app design
VIPER is our end candidate, which is mostly interesting because it doesn’t come from the MV(X) category.
By now, you must concur that the grainy in responsibilities is very fine. VIPER creates an additional iteration on the idea of unscrambling responsibilities, and this time we have five layers.
VIPER
Interactor — encloses business logic related to the data or networking, like creating new instances of datas or fetching them from the server. For those reasons you’ll utilize some Services and Managers which are not measured as a part of VIPER module but rather an external addiction.
Presenter — encloses the UI related business logic, raises methods on the Interactor.
Entities — your basic data objects, not the data access layer, since that is a responsibility of the Interactor.
Router — liable for the segues between the VIPER modules.
Essentially, VIPER module can be a one screen or the whole user story of your application — imagine of authentication, which can be one screen or numerous connected ones. How tiny are your “LEGO” blocks? — It’s up to you.
If we evaluate it with the MV(X) kind, we’ll observe a few differences of the distribution of responsibilities:
Model logic moved into the Interactor with the Entities as dumb data structures.
Only the UI representation duties of the Controller/Presenter/ViewModel shifted into the Presenter, but not the data changing abilities.
VIPER is the first pattern which plainly addresses navigation liability, which is invented to be resolved by the Router.
Accurate way of doing routing is a challenge for the iOS applications, the MV(X) patterns basically don’t address this issue.
The example does not envelop routing or interaction between modules, as those topics are not covered by the MV(X) patterns at each one.
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
Features:
Distribution — certainly, VIPER is a champion in distribution of responsibilities.
Testability —no disclosures here, better distribution and testability.
Ease of use — lastly, two above come in cost of maintainability as you already guessed. You cover to write vast amount of interface for classes with very tiny responsibilities.
In this blog we learned about the important iOS architecture design patterns, I hope this blog is useful to you.
Visit our ios App development Services.
Leave a Reply