Skip to content

Instantly share code, notes, and snippets.

@vtsoup
Last active September 3, 2018 15:28
Show Gist options
  • Save vtsoup/f1f79d19d6f8e58396bde8847c09a62e to your computer and use it in GitHub Desktop.
Save vtsoup/f1f79d19d6f8e58396bde8847c09a62e to your computer and use it in GitHub Desktop.
MVP

AppDelegate -> Router -> MVP/C

graph TD
D[Deep link]-->R
A[App Delegate]-->R
R{Router} -->V(View)
P(Presenter) -- View Model --> V(View)
V -- Event Delegate --> P
P --> U1((Use Case))
U1-.->P
U1-->S[Persistent Storage]
S-. Model Change Notification .->P
U1-->A1>API Request]
A1-.->U1

Diagram

Folder Structure

If a feature is named Dashboard, the folder structure in the XCode project would like example below. If the view is more complicated, there may be other .xib and .swift files.

Dashboard ├── DashboardProtocols.swift ├── DashboardPresenter.swift ├── DashboardViewController.swift ├── DashboardViewController.storyboard └── ...

DashboardViewController will conform to DashboardViewProtocol.

  • Entry point for Router
  • Creates the Presenter and has a strong reference to it

DashboardPresenter will conform to DashbboardPresenterProtocol.

  • Has a weak reference to the View

Sequence Diagram

sequenceDiagram
	Router->>View: create with params
	View->>View: create Presenter
    View->>Presenter: set me as your View
    View->>Router: UIViewController
    Router->>View: Router.show()
    View->>View: viewDidLoad()
    View->>Presenter: viewIsReady()
    Note over Presenter: load the data with params
    Presenter->>Use Cases: request
    Use Cases->>API: API request
    Presenter-->>Storage: (listen for model changes)
    View->>Presenter: pageViewStarted()
    Use Cases-->>Presenter: status
    API-->>Use Cases: API response
    Use Cases->>Use Cases: parse response, handle errors
    Use Cases-->>Presenter: status
    Use Cases->>Storage: Persist and notify
    Storage-->>Presenter: received model change notification
    Presenter->>Presenter: fetch entities from Storage
    Presenter->>Presenter: transform to view model
    Presenter->>View: update display with view model
    Note over View: Some time later...
    View->>Presenter: listItemWasTapped(id)
    Presenter->>Router: Router.route("/details", id)
    Note over View: ...or...
    View->>Presenter: refreshControlChanged()
    Presenter->>Use Cases: (same as before) 

Deep-linking and App Delegate -> Router

graph TD
A[App Delegate]-->R[Router]
R{Router} -->P1(Presenter)
D[Deep link]-->R

Diagram

The Router is app specific and is not shared between our different apps. The Router will contain a dictionary of routes. A global singleton instance named router is declared in the AppDelegate.

The Router knows about all of the Presenters and their maker functions. The Router will receive arguments from the callers of the routing action. It will use pass these arguments to the Presenters' maker functions.

Router -> Presenters

graph TD
R{Router} -->P1(Presenter A)
R-->P2(Presenter B)
R-->P3(Presenter C)
R-->P4(Presenter D)
P2-. route to C .->R

Diagram

The Router knows about all of the Presenters and their maker functions. The Router will receive arguments from the callers of the routing action. It will pass these arguments to the Presenters' maker functions.

The Presenter is the entry point for the Router. Router, navigation controllers, UIViewControllers, and other Presenters do not instantiate a View directly. For example, in the diagram above, Presenter B receives a user event from its View . The proper action is to show Presenter C. Instead of instantiating Presenter C directly, it will ask the Router to do it. This flow will ensure that Presenters are not tightly coupled to each other.

Each component's Presenter will conform to the PresenterProtocol and expose a var viewController: UIViewController computed property that the Router can present modally or push onto the navigation stack.

The Presenter should provide static maker function(s) that take all the needed arguments (usually in the form of entity ids) to request all the data required to populate the View's display controls. These static maker functions should also allow for dependency injection to ensure that they can be fully mocked and tested.

TODO: Example maker/create function?

Presenter -> View

graph TD
P(Presenter) -- View Model --> V(View)
V -. Event Delegate .-> P
V -->G(UIViewController)
G-->E[Storyboard]
G-->F[Xibs]
P --> C(Coordinator)

Diagram

A Presenter can have 1 and only 1 View.

Presenter -> Coordinator

graph TD
P(Presenter) --> V(View)
P --> C(Coordinator)

Diagram

Coordinators are optional. A Presenter can have 0 or 1 Coordinator. In cases where the View has no data requirements or is only dependent on 1 or 2 Use Cases, the Presenter can do the work of the Coordinator. As far as we can tell from our initial design meetings, these low-data backed views are rare; however, we wanted leave it as an option to exclude a Coordinator (thus its associated protocols and tests) when appropriate. This optionality may be removed once we have more experience implementing this architecture.

Coordinator -> Use Cases

graph TD
P(Presenter) --> V(View)
P --> C(Coordinator)
C --> U1((Use Case))
U1-. Status .->C
U1-->A1>API Request]
A1-.->U1
C --> U2((Use Case))
U2-. Status .->C
U2-->A2>API Request]
A2-.->U2
C --> U3((Use Case))
U3-. Status .->C
U3-->A3>API Request]
A3-.->U3

Diagram

A Coordinator will aggregate (or coordinate) 1 to many Use Cases in order to fulfill the data requirements needed by the UI layers.

Model Layer

The model layer consist of Models, Use Cases, API Requests, and Persistent Storage.

Use Case

Model

API Request

Persistent Storage

Use Case -> API Request + Persistent Storage

graph TD
C(Coordinator) --> U1((Use Case))
U1-->S(Persistent Storage)
U1-->A1>API Request]
A1-.->U1

Diagram

Persistent Storage -> Presenter

graph TD
P(Presenter) --> V(View)
P --> C(Coordinator)
C --> U1((Use Case))
U1-->S(Persistent Storage)
S-. Model Change Notification .->P

Diagram

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment