{"id":121,"date":"2017-02-05T11:04:19","date_gmt":"2017-02-05T17:04:19","guid":{"rendered":"https:\/\/chibicode.org\/?p=121"},"modified":"2017-02-06T13:29:06","modified_gmt":"2017-02-06T19:29:06","slug":"beyond-view-controllers","status":"publish","type":"post","link":"https:\/\/chibicode.org\/?p=121","title":{"rendered":"Beyond View Controllers"},"content":{"rendered":"<p><strong>In a nutshell:<\/strong> Remove from ViewControllers all tasks which are not view-related.<\/p>\n<p><strong>Quick Links:<\/strong><br \/>\n<a href=\"https:\/\/chibicode.org\/wp-content\/uploads\/2017\/02\/Architecture_Diagram.pdf\" target=\"_blank\">Architecture Diagram PDF<\/a><br \/>\n<a href=\"https:\/\/github.com\/JoshuaSullivan\/BeyondViewControllers\" target=\"_blank\">Example Project<\/a><\/p>\n<h2>Problems with ViewControllers in MVC<\/h2>\n<p>The View Controller is typically the highest level of organization in the iOS standard MVC app. This tends to make them accumulate a wide variety of functionality that causes them to grow in both size and complexity over the course of a project&#8217;s development. Here are the basic issues I have with the role of view controllers in the &#8220;standard&#8221; iOS MVC pattern:<\/p>\n<ul>\n<li>Handle too many tasks:\n<ul>\n<li>View hierarchy management<\/li>\n<li>API Interaction<\/li>\n<li>Data persistence<\/li>\n<li>Intra-Controller data flow<\/li>\n<\/ul>\n<\/li>\n<li>Need to have knowledge of other ViewControllers to pass state along.<\/li>\n<li>Difficult to test business logic tied to the view structure.<\/li>\n<\/ul>\n<h2>Guiding Principles of Coordinated MVC<\/h2>\n<h3>Tasks, not Screens<\/h3>\n<p>The architecture adds a level of organization above the View Controller called the <code>Coordinator<\/code> layer. The <code>Coordinator<\/code> objects break the user flow of your app into discrete tasks that can be performed in an arbitrary order. Example tasks for a simple shopping app might be: <em>Login, Create Account, Browse Content, Checkout, and Help.<\/em><\/p>\n<p>Each <code>Coordinator<\/code> manages the user flow through a single task. It is important to note that there is not a unique relationship between <code>Coordinators<\/code> and the screens they manage; multiple <code>Coordinators<\/code> can call upon the same screen as part of their flow. We want a <code>Coordinator<\/code> to completely define a task from beginning to completion, only changing to a different <code>Coordinator<\/code> when the task is complete or the user takes action to switch tasks in mid-flow.<\/p>\n<p><strong>Rationalle:<\/strong> When View Controllers must be aware of their role within a larger task, they tend to become specialized for that role and tightly coupled to it. Then, when the same view controller is needed elsewhere in the app, the developer is faced with the task of either putting branching logic all over the class to handle the different use cases or duplicating the class and making minor changes to it for each use case.<\/p>\n<p>When combined with <em>Model Isolation<\/em> and <em>Mindful State Mutation<\/em>, having the control flow of the app determined at a higher level than the view controller solves this scenario, allowing the view controller to be repurposed more easily.<\/p>\n<h3>Model Isolation<\/h3>\n<p>View Controllers must define all of their data requirements in the form of a <code>DataSource<\/code> protocol. Every view controller will have a <code>var dataSource: DataSource?<\/code> property that will be its sole source of external information. Essentially, this is the same as a View Model in the MVVM pattern.<\/p>\n<p><strong>Rationale:<\/strong> When View Controllers start reaching out directly to the Model or service-layer objects (API clients, persistence stacks, etc.) they begin to couple the model tightly to their views, making testing increasingly difficult.<\/p>\n<h3>Mindful State Mutation<\/h3>\n<p>View Controllers shall define all of their external state mutations in the form of a <code>Delegate<\/code> protocol. Every view controller will have a <code>var delegate: Delegate?<\/code> property that will be the only object that the View Controller reaches out to in order to mutate external state. That is to say, the View Controller can take whatever actions are necessary to ensure proper view consistency, but when there is a need to change to a new screen or take some other action that takes place &#8220;outside&#8221; itself, it invokes a method on its delegate.<\/p>\n<p><strong>Rationale:<\/strong> In the traditional MVC architecture, View Controllers become tightly coupled to each other, either by instantiating their successor view controller and pushing it onto a Nav Controller, or by invoking a storyboard segue and then passing model and state information along in <code>prepareForSegue()<\/code>. This coupling makes it much more difficult to test that the user flow of your app is working as expected, particularly in situations with a lot of branching logic.<\/p>\n<h2>The Architecture in Depth<\/h2>\n<p><a href=\"https:\/\/chibicode.org\/wp-content\/uploads\/2017\/02\/Architecture_Diagram.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/chibicode.org\/wp-content\/uploads\/2017\/02\/Architecture_Diagram_Preview.png\" alt=\"\" width=\"448\" height=\"448\" class=\"alignnone\" \/><\/a><br \/>\n<a href=\"https:\/\/chibicode.org\/wp-content\/uploads\/2017\/02\/Architecture_Diagram.pdf\" target=\"_blank\">Download PDF Version<\/a><\/p>\n<h3>Task<\/h3>\n<p>A global enum that contains a case for every possible user flow within the app. Each task should have its own TaskCoordinator.<\/p>\n<h3>App Coordinator<\/h3>\n<p>The ultimate source of truth about what state the app should be in. It manages the transitions between the <code>TaskCoordinator<\/code> objects. It decides which <code>Task<\/code> should be started on app launch (useful when deciding whether to present a login screen, or take the user straight to content). The <code>AppCoordinator<\/code> decides what to do when a <code>Task<\/code> completes (in the form of a delegate callback from the currently active <code>TaskCoordinator<\/code>).<\/p>\n<p>The <code>AppCoordinator<\/code> holds a reference to the root view controller of the app and uses it to parent the various <code>TaskCoordinator<\/code> view controllers. If not root view controller is specified, the AppCoordinator assumes it is being tested and does not attempt to perform view parenting.<\/p>\n<p>The <code>AppCoordinator<\/code>creates and retains the service layer objects, using dependency injection to pass them to the <code>TaskCoordinators<\/code> which then inject them into the <code>ViewModels<\/code>.<\/p>\n<h3>Task Coordinator<\/h3>\n<p>Manages the user flow for a single <code>Task<\/code> through an arbitrary number of screens. It has no knowledge of any other <code>TaskCoordinator<\/code> and interacts with the <code>AppCoordinator<\/code> via a simple protocol that includes methods for completing its <code>Task<\/code> or notifying the <code>AppCoordinator<\/code> that a different <code>Task<\/code> should be switched to.<\/p>\n<p><code>TaskCoordinators<\/code> create and manage the <code>ViewModel<\/code> objects, assigning them as appropriate to the <code>dataSource<\/code> of the varous View Controllers that it manages.<\/p>\n<h3>Service Layer<\/h3>\n<p>Objects in the service layer encapsulate business logic that should be persisted and shared between objects. Some examples might be a <code>UserAuthenticationService<\/code> that tracks the global auth state for the current user or an <code>APIClient<\/code> that encapsulates the process of requesting data from a server.<\/p>\n<p><strong>Service layer objects should <em>never<\/em> be accessed directly by View Controllers!<\/strong> Only <code>ViewModel<\/code> and <code>Coordinator<\/code> objects are permitted to access services. If a View Controller needs information from a service, it should declare the requirement in its <code>DataSource<\/code> protocol and allow the <code>ViewModel<\/code> to fetch it.<\/p>\n<p>Avoid giving in to the siren call of making your service layer objects as singletons. Doing so will make testing your <code>Coordinator<\/code> and <code>ViewModel<\/code> objects more difficult, because you will not be able to substitute mock services that return a well-defined result.<\/p>\n<p>If you want to do data\/API response mocking\u2014say because the API your app relies on won&#8217;t be finished for another couple of weeks\u2014these objects are where it should occur. You can build finished business logic into your <code>ViewModel<\/code> and <code>Coordinator<\/code> objects that doesn&#8217;t need to change at all once you stop mocking data and connect to a live API.<\/p>\n<h3>View Model<\/h3>\n<p><code>ViewModel<\/code> objects are created and owned by <code>TaskCoordinators<\/code>. They should receive references to the service layer objects they require in their constructors (dependency injection). A single <code>ViewModel<\/code> may act as the <code>DataSource<\/code> for multiple View Controllers, if sharing state between those controllers is advantageous.<\/p>\n<p><code>ViewModels<\/code> should only send data down to the View Controller, and should not be the recipient of user actions. The <code>TaskCoordinator<\/code> that owns the <code>ViewModel<\/code> and is acting as the View Controller&#8217;s delegate will mutate the <code>ViewModel<\/code> with state changes resulting from user actions.<\/p>\n<h2>Putting it into Practice<\/h2>\n<p>I have created a simple &#8220;Weather App&#8221; example project that shows the architecture in action:<\/p>\n<p><a href=\"https:\/\/github.com\/JoshuaSullivan\/BeyondViewControllers\" target=\"_blank\">Example Project<\/a><\/p>\n<p>Here&#8217;s how to follow flow:<\/p>\n<ol>\n<li>In the <code>AppDelegate<\/code> you can see the <code>AppCoordinator<\/code> being instantiated and handed the root view controller.<\/li>\n<li>In the <code>AppCoordinator<\/code>&#8216;s <code>init<\/code> method, observe how it checks to see if the user has &#8220;logged in&#8221;.\n<ul>\n<li>If the user is not logged in, the user is directed to the Login task to complete logging in.<\/li>\n<li>If the user is logged in, then they are taken directly to the Forecast task.<\/li>\n<\/ul>\n<\/li>\n<li>When tasks have completed their objective, they call their delegate <code>taskCooordinator(finished:)<\/code> method. This triggers the <code>AppCoordinator<\/code> to determine what the next task is. In a fully-fledged app, there could be a considerable amount of state inspection as part of this process.<\/li>\n<\/ol>\n<h2>Quick Rules for Conformance<\/h2>\n<ol>\n<li>No view controller should access information except from its <code>dataSource<\/code> (View Model).<\/li>\n<li>No view controller should attempt to mutate state outside of itself except through its <code>delegate<\/code> (usually a <code>TaskCoordinator<\/code>).<\/li>\n<li>No view controller should have knowledge of any other view controller save those which it directly parents (embed segue or custom containment).<\/li>\n<li>View Controllers should never access the Service layer directly; always mediate access through the <code>delegate<\/code> and <code>dataSource<\/code>.<\/li>\n<li>A view controller may be used by any number of <code>TaskCoordinator<\/code> objects, so long as they are able to fulfill its data and delegation needs.<\/li>\n<\/ol>\n<h3>Thanks<\/h3>\n<p>A big thank you to Soroush Khanlou and Chris Dzombak and their fantastic <a href=\"https:\/\/fatalerror.fm\">Fatal Error<\/a> podcast for giving me inspiration to create this.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In a nutshell: Remove from ViewControllers all tasks which are not view-related. Quick Links: Architecture Diagram PDF Example Project Problems with ViewControllers in MVC The View Controller is typically the highest level of organization in the iOS standard MVC app. This tends to make them accumulate a wide variety of functionality that causes them to &hellip; <a href=\"https:\/\/chibicode.org\/?p=121\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Beyond View Controllers&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-121","post","type-post","status-publish","format-standard","hentry","category-code"],"_links":{"self":[{"href":"https:\/\/chibicode.org\/index.php?rest_route=\/wp\/v2\/posts\/121","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/chibicode.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/chibicode.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/chibicode.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/chibicode.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=121"}],"version-history":[{"count":6,"href":"https:\/\/chibicode.org\/index.php?rest_route=\/wp\/v2\/posts\/121\/revisions"}],"predecessor-version":[{"id":128,"href":"https:\/\/chibicode.org\/index.php?rest_route=\/wp\/v2\/posts\/121\/revisions\/128"}],"wp:attachment":[{"href":"https:\/\/chibicode.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=121"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/chibicode.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=121"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/chibicode.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=121"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}