iOS App Architectures VIPER Rajat Datta CocoaCoder
What is the Problem? • Massive View Controllers • Model objects (Core Data) are typically quite simple • Views are combinations of UIKit components • do not have access to models • Only interaction with Controllers is via IBActions • View Controllers get everything else!
• What goes into View Controllers? • Data sources for Views (UITableView DataSource) • Business Logic • Application flow • transition between view controllers • VIPER is one proposal for a different app structure.
VIPER • V(iew) I(nteractor) P(resenter) E(ntity) R(outer) Wireframe Entity View Presenter Interactor Entity Data Store
Views • UIKit components (UILabel, UITableView, …) • Presents a public interface that can be used to drive the UI • Views are completely passive; they don’t ask for the data to be shown. • implemented as public methods (ie. showName:(NSString *)name) • Defines an event interface triggered by IBActions, gestures • implemented as @protocol methods • ie. - (void)addEventAction:(id)sender; • The View Controller implements these two interfaces
Presenter (PONSO) • Drives the UI using the View public and event interfaces • Sends requests to the Interactor • with data entered in the View • for data to be presented in the View • Data are simple models, not entities • mostly read/only; no business logic can be applied • absolutely no notion of persistence is applied to these data models • Sends requests to the Wireframe for UI transitions • ie. for Add action, request the Wireframe to transition to Add View.
• Presenter can be a number of related objects • View:showName: method with NSString * • View:showTable: method with DataSource object • object is owned by Presenter
Wireframe (PONSO) • Owns the UIWindow, UINavigationController and all UIViewControllers • Presents an action interface for the Presenter • Responsible for transition animations. • Can work with Storyboards • for more complex flows • use Storyboard IDs and instantiate in code
Interactor (PONSO) • Each Interactor represents a single use-case in the app • All business logic is contained in the Interactor • Totally independent of UI, and can/should be covered by test cases. • This is where the core of all application dependencies will be found. • Fetches Entities from the Data Store • Passes and receives Models from the Presenter
Entities (PONSO) • Created by the Data Store • Object contents are modified by the Interactor • Is not a managed object. Entities do not know how to persist themselves. • Just data structures. App dependent logic should be in an Interactor.
Data Store • Responsible for presenting Entities to the Interactor • All persistence decisions are made here. • Can be replaced by a test double (mock). • Can be independently tested.
Demo App • Journal App • Journal Entry • Date and Time • Text • Last edit Date and Time • Use Cases • Add Journal Entry • Show Journal Entries • Edit an existing Journal Entry • Persistence • Core Data • Cloudkit?
Views • List View • Presenter starts the view with a datasource containing list of entries • from date, to date fields • when set, calls back via protocol method to the Presenter, which presents the view with a new datasource. • Edit Action (gesture on Table View?) • call back via protocol method to the Presenter, along with which journal entry selected. • Add Action (button?) • call back via protocol method to the Presenter.
Presenter • Initially, call Interactor and fetch journal entries for last 30 days • Call View:showJournalList: with datasource containing list of journal entries; • callback from view with from and to dates; fetch data from Interactor and call View:showJournalList with that list • callback from view with edit action and journal entry; call to Wireframe to switch UI to Edit mode; • callback from view with add action; call to wireframe to switch UI to Add mode.
Interactor • For View Action • method to fetch all journal articles for last 30 days; also returns from date, to date • method which, given from date, to date, returns the list of journal entries within that time (inclusive). • For Add action • method which, given a date and time, returns a new, empty, journal entry • contains only the id for the entry (unix timestamp) • method which, given id and journal entry, calls the data store to save a new journal entry.
Entity • Data structure that Interactor passes along to the Data Store and back; • id (unix timestamp) for the journal entry. This is expected to be unique. • text: NSAttributedString • to be complete, should support images and formatting information. • last edit time. • In this case, this can also be the model • shared between the Interactor and the Presenter.
Wireframe • Switch to List View • Switch to Edit View • Switch to Add View
Data Store • in DataStore:init method, setup the Core Data sqlite store • DataStore:newJournalEntry • DataStore:fetchJournalEntriesFromDate:toDate: • DataStore:changedJournalEntry:
Edge Cases • Out of band information • network access status; simple case • Push notification informs app of new information • overlay Presenter, on top of existing Presenter • overlay Presenter can ask Wireframe to change the UI. • Presenter can offer method that puts a Presenter to sleep • For games, stop the game clock.