Unidirectional Data Flow

in Swift





Ian Terrell

ianterrell.com
Fortune's Best Workplaces Glassdoor Award Appy Award
Top App Developers Best New App Inc 500 Company
Webby Award Honoree Excellence in Tech Digital Health Award
W3 Award Communicator Award Gold Stevie Award
VA Fantastic 50 MobileWebAward Business of the Year
     ┌───────┐
     │       │
  ┌──│       │◀─┐
  │  │       │  │
  │  └───────┘  │
  │             │
  │  ┌───────┐  │
  │  │       │  │
  └─▶│       │──┘
     │       │
     └───────┘

?

What is a view controller?

When you call something a controller... nothing is out of scope, since its purpose is to control things.
— Soroush Khanlou

MVC

MVC

                     ┌──────────────────┐
                     │                  │
                     │                  │
                     │                  │
                     │                  │
                     │                  │
                     │                  │
                     │                  │
                     │                  │
                     │                  │
                     │                  │
                     │                  │
                     └─View Controller──┘
						

   ┌────┬─────────┐
   │####│~~!@$)_+_│
   │####│+[][;;'; │
   ├────┴──┬──────┤
   │*******│      │
   ├──┬────┴──────┤
   │  │~~~~~~~~~~~│
   ├──┴───────┬───┤
   │          └───┤
   └─────View─────┘



                 ◀────▶

                 ◀────▶

                 ◀────▶

                 ◀────▶

                                           ┌──────────────┐
                                           │              │
                                           │              │
                                           │              │
                                           │              │
                                           │              │
                                           │              │
                                           │              │
                                           │              │
                                           └────Model─────┘




                                       ◀────▶

                                           ┌──────────────┐
                                           │   Database   │
                                           ├ ─ ─ ─ ─ ─ ─ ─│
                                           │     API      │
                                           ├ ─ ─ ─ ─ ─ ─ ─│
                                           │    Cache     │
                                           ├ ─ ─ ─ ─ ─ ─ ─│
                                           │ Notification │
                                           ├ ─ ─ ─ ─ ─ ─ ─│
                                           └────Model─────┘


                                       ◀────▶

                                       ◀────▶

                                       ◀────▶

                                       ◀────▶
																																	


                           ──┬┬─│
                          ───┼┼┬┼─▷─
                             │││└──┐
                           ◀─┼┼┤   │
                           ◁─┼┼┼─▶ │
                          ───┤││ ┌──
                          ▲  │└┼─▶
                         ─┴┬─┴─┤─┘
                           │   └──▷

MVC

final class HomeViewController: UIViewController {

    @IBOutlet var headerView: UIView!

    @IBOutlet var signInButton: UIButton!

    @IBOutlet var signOutButton: UIButton!

    // ...


    var dataService: DataService!

    var apiService: APIService!

    var cache: Cache!

    // ...


    @IBAction func signIn() {

        // ...

    }

    // ...

}

Massive View Controllers

MVVM

MVVM

                  ┌──────────┐
                  │          │
                  │          │
                  │          │
                  │          │
                  │          │
                  │          │
                  │          │
                  │          │
                  │          │
                  │          │
                  │          │
                  └Controller┘
						

┌────┬─────────┐
│####│~~!@$)_+_│
│####│+[][;;'; │
├────┴──┬──────┤
│*******│      │
├──┬────┴──────┤
│  │~~~~~~~~~~~│
├──┴───────┬───┤
│          └───┤
└─────View─────┘
						





              ◀────▶







                                ┌──────────────────┐
                                │                  │
                                │                  │
                                │                  │
                                │                  │
                                │                  │
                                │                  │
                                │                  │
                                │                  │
                                │                  │
                                │                  │
                                │                  │
                                └────View Model────┘





                            ◀────▶








                                                      ┌──────────────┐
                                                      │   Database   │
                                                      ├ ─ ─ ─ ─ ─ ─ ─│
                                                      │     API      │
                                                      ├ ─ ─ ─ ─ ─ ─ ─│
                                                      │    Cache     │
                                                      ├ ─ ─ ─ ─ ─ ─ ─│
                                                      │ Notification │
                                                      ├ ─ ─ ─ ─ ─ ─ ─│
                                                      └────Model─────┘




                                                  ◀────▶

                                                  ◀────▶

                                                  ◀────▶

                                                  ◀────▶







              ◀──── ─ ─ ─ ─ ─────▶



              ◀──── ─ ─ ─ ─ ─────▶

              ◀──── ─ ─ ─ ─ ─────▶





                                      ──┬┬─│
                                     ───┼┼┬┼─▷─
                                        │││└──┐
                                      ◀─┼┼┤   │
                                      ◁─┼┼┼─▶ │
                                     ───┤││ ┌──
                                     ▲  │└┼─▶
                                    ─┴┬─┴─┤─┘
                                      │   └──▷


MVVM

final class HomeViewController: UIViewController {

    @IBOutlet var headerView: UIView!

    @IBOutlet var signInButton: UIButton!

    @IBOutlet var signOutButton: UIButton!

    // ...


    var viewModel: HomeViewModel!

}

Unidirectional Data Flow

Unidirectional Data Flow


				                  ┌──────────┐
				                  │          │
				                  │          │
				                  │          │
				                  │          │
				                  │          │
				                  │          │
				                  │          │
				                  │          │
				                  │          │
				                  │          │
				                  │          │
				                  └Controller┘


				┌────┬─────────┐
				│####│~~!@$)_+_│
				│####│+[][;;'; │
				├────┴──┬──────┤
				│*******│      │
				├──┬────┴──────┤
				│  │~~~~~~~~~~~│
				├──┴───────┬───┤
				│          └───┤
				└─────View─────┘

			

				                                ┌──────────────────┐
				                                │                  │
				                                │                  │
				                                │                  │
				                                │                  │
				                                │                  │
				                                │                  │
				                                │                  │
				                                │                  │
				                                │                  │
				                                │                  │
				                                │                  │
				                                └──────Store───────┘




				                            ◀─────













				              ◀─────



















				              ─────▶










				                            ─────▶



				                                       ──┬┬─│
				                                      ───┼┼┬┼─▷─
				                                         │││└──┐
				                                       ◀─┼┼┤   │
				                                       ◁─┼┼┼─▶ │
				                                      ───┤││ ┌──
				                                      ▲  │└┼─▶
				                                     ─┴┬─┴─┤─┘
				                                       │   └──▷





				                                     ┌┐┌┐┌┐┌┐┌┐
				                                     ││││││││││
				                                     ││││││││││
				                                     ││││││││││
				                                     ││││││││││
				                                     ││││││││││
				                                     ││││││││││
				                                     │└┘└┘└┘└┘│
				                                     ◀────────┘

				                                     

Flux

Flux

Flux

                                   ┌───────────┐
                                   │           │
                        ┌──────────│  Action   │◀─────────┐
                        │          │           │          │
                        │          └───────────┘          │
                        ▼                                 │
┌───────────┐    ┌────────────┐    ┌───────────┐    ┌───────────┐
│           │    │            │    │           │    │           │
│  Action   │───▶│ Dispatcher │───▶│   Store   │───▶│   View    │
│           │    │            │    │           │    │           │
└───────────┘    └────────────┘    └───────────┘    └───────────┘
							

Flux

        ┌────────────────────Action────────────────────┐
        │                                              │
        ▼                                              │
 ┌────────────┐           ┌───────────┐          ┌───────────┐
 │            │           │           │          │           │
 │ Dispatcher │──Action──▶│   Store   │──State──▶│   View    │
 │            │           │           │          │           │
 └────────────┘           └───────────┘          └───────────┘

Flux

Flux

┌────────────────┐
│                │
│  Hello, Bob!   │
│         ───    │
│                │
│                │
│   ┌────────┐   │
│   │Sign Out│   │
│   └────────┘   │
└────────────────┘

Flux

    struct State {

        var currentUser: User?

    }


    struct User {

        let name: String

    }

┌────────────────┐
│                │
│  Hello, Bob!   │
│         ───    │
│                │
│                │
│   ┌────────┐   │
│   │Sign Out│   │
│   └────────┘   │
└────────────────┘

Flux

    enum Action {

        case signIn(User)

        case signOut

    }

┌────────────────┐
│                │
│  Hello, Bob!   │
│         ───    │
│                │
│                │
│   ┌────────┐   │
│   │Sign Out│   │
│   └────────┘   │
└────────────────┘

Flux

    final class Store {

        var state: State

        var subscriber: Subscriber?


        init(initialState: State) {

            state = initialState

        }


        func dispatch(action: Action) {

            // state = update state ...

            subscriber?.newState(state)

        }


        func subscribe(view: Subscriber) {

            subscriber = view

        }

    }

┌────────────────┐
│                │
│  Hello, Bob!   │
│         ───    │
│                │
│                │
│   ┌────────┐   │
│   │Sign Out│   │
│   └────────┘   │
└────────────────┘

Flux


          ┌────────────────────────────────────────────────┐
          │                                                │
          │               ┌───────────┐     ┌───────────┐  │
          │               │           │     │           │  │
          │          ┌───▶│   Store   │────▶│   View    │──┤
          │          │    │           │     │           │  │
          ▼          │    └───────────┘     └───────────┘  │
   ┌────────────┐    │    ┌───────────┐     ┌───────────┐  │
   │            │    │    │           │     │           │  │
   │ Dispatcher │────┼───▶│   Store   │────▶│   View    │──┤
   │            │    │    │           │     │           │  │
   └────────────┘    │    └───────────┘     └───────────┘  │
                     │    ┌───────────┐     ┌───────────┐  │
                     │    │           │     │           │  │
                     └───▶│   Store   │────▶│   View    │──┘
                          │           │     │           │
                          └───────────┘     └───────────┘

Redux

Redux


          ┌────────────────────────────────────────────────┐
          │                                                │
          │               ┌───────────┐     ┌───────────┐  │
          │               │           │     │           │  │
          │          ┌───▶│   Store   │────▶│   View    │──┤
          │          │    │           │     │           │  │
          ▼          │    └───────────┘     └───────────┘  │
   ┌────────────┐    │    ┌───────────┐     ┌───────────┐  │
   │            │    │    │           │     │           │  │
   │ Dispatcher │────┼───▶│   Store   │────▶│   View    │──┤
   │            │    │    │           │     │           │  │
   └────────────┘    │    └───────────┘     └───────────┘  │
                     │    ┌───────────┐     ┌───────────┐  │
                     │    │           │     │           │  │
                     └───▶│   Store   │────▶│   View    │──┘
                          │           │     │           │
                          └───────────┘     └───────────┘

Redux


          ┌────────────────────────────────────────────────┐
          │                                                │
          │                                 ┌───────────┐  │
          │                                 │           │  │
          │                     ┌──────────▶│   View    │──┤
          │                     │           │           │  │
          ▼                     │           └───────────┘  │
   ┌────────────┐         ┌───────────┐     ┌───────────┐  │
   │            │         │           │     │           │  │
   │ Dispatcher │────────▶│   Store   │────▶│   View    │──┤
   │            │         │           │     │           │  │
   └────────────┘         └───────────┘     └───────────┘  │
                                │           ┌───────────┐  │
                                │           │           │  │
                                └──────────▶│   View    │──┘
                                            │           │
                                            └───────────┘

Redux


                 ┌─────────────────────────────────────────┐
                 │                                         │
                 │                          ┌───────────┐  │
                 │                          │           │  │
                 │              ┌──────────▶│   View    │──┤
                 │              │           │           │  │
                 │              │           └───────────┘  │
                 │        ┌───────────┐     ┌───────────┐  │
                 │        │           │     │           │  │
                 └───────▶│   Store   │────▶│   View    │──┤
                          │           │     │           │  │
                          └───────────┘     └───────────┘  │
                                │           ┌───────────┐  │
                                │           │           │  │
                                └──────────▶│   View    │──┘
                                            │           │
                                            └───────────┘

Redux


                 ┌─────────────────────────────────────────┐
                 │                                         │
                 │                          ┌───────────┐  │
                 │                          │           │  │
                 │              ┌──────────▶│   View    │──┤
                 │              │           │           │  │
                 │              │           └───────────┘  │
                 │        ┌───────────┐     ┌───────────┐  │
                 │        │           │     │           │  │
                 └───────▶│   Store   │────▶│   View    │──┤
                          │           │     │           │  │
                          └│─▲────────┘     └───────────┘  │
                   ┌───────▼─│┐ │           ┌───────────┐  │
                   │          │ │           │           │  │
                   │ Reducer  │ └──────────▶│   View    │──┘
                   │          │             │           │
                   └──────────┘             └───────────┘

Reducer

        (State, Action) -> State

Reducer

    // Sequence

   

    func reduce<Result>(

        _ initialResult: Result,

        _ nextPartialResult: (Result, Self.Iterator.Element) -> Result

    ) -> Result

Reducer

    var actionStream: [Action]

   

    func reduce<Result>(

        _ initialResult: Result,

        _ nextPartialResult: (Result, Action) -> Result

    ) -> Result

Reducer

    typealias State = Result


    func reduce(

        _ initialResult: State,

        _ nextPartialResult: (State, Action) -> State

    ) -> State

Reducer

        _ nextPartialResult: (State, Action) -> State

Reducer

        (State, Action) -> State

Reducer

    static func reduce(_ state: State, _ action: Action) -> State {

        var state = state


        switch action {

        case .signOut:

            state.currentUser = nil

        case .signIn(let user):

            state.currentUser = user

        }


        return state

    }

Reducer

    class ReduceTests: XCTestCase {

        let user = User(name: "Bob")

        let signedOut = State(currentUser: nil)

        let signedIn = State(currentUser: User(name: "Bob"))


        func testSignIn() {

            let newState = reduce(signedOut, Action.signIn(user))

            XCTAssertEqual(signedIn, newState)

        }


        func testSignOut() {

            let newState = reduce(signedIn, Action.signOut)

            XCTAssertEqual(signedOut, newState)

        }

    }

  ──┬┬─│
 ───┼┼┬┼─▷─
    │││└──┐
  ◀─┼┼┤   │
  ◁─┼┼┼─▶ │
 ───┤││ ┌──
 ▲  │└┼─▶
─┴┬─┴─┤─┘
  │   └──▷
┌┐┌┐┌┐┌┐┌┐
││││││││││
││││││││││
││││││││││
││││││││││
││││││││││
││││││││││
│└┘└┘└┘└┘│
◀────────┘

github.com/ReSwift

ReSwift

    struct AppState: StateType {

        var user: User?

    }


    struct User {

        let name: String

    }

ReSwift

    enum AuthenticationAction: Action {

        case signIn(User)

        case signOut

    }

ReSwift

    final class AppReducer: Reducer {

        func handleAction(action: Action, state: AppState?) -> AppState {

            let state = state ?? AppState()

            return AppState(

                user: userReducer(action: action, state: state)

            )

        }

    }

ReSwift

    extension AppReducer {

        func userReducer(action: Action, state: AppState) -> User? {

            guard let action = action as? AuthenticationAction else {

                return state.user

            }

            switch action {

            case .signIn(let user):

                return user

            case .signOut:

                return nil

            }

        }

    }

ReSwift

    let store = Store<AppState>(reducer: AppReducer(), state: nil)

ReSwift

    final class HomeViewController: UIViewController, StoreSubscriber {

        var store: Store<AppState>


        override func viewWillAppear(_ animated: Bool) {

            store.subscribe(self)

        }


        override func viewWillDisappear(_ animated: Bool) {

            store.unsubscribe(self)

        }


        func newState(state: AppState) {

            // render state to view

        }

    }

ReSwift

    extension HomeViewController {

        @IBAction func signOut(sender: AnyObject) {

            store.dispatch(AuthenticationAction.signOut)

        }

    }

The best-laid schemes

Example
Sign In Form

Sign In Form

struct SignInState {

    var isSigningIn = false

    var serverError: String?


    var email: String?

    var emailEditedOnce = false


    var password: String?

    var passwordEditedOnce = false

}

Sign In Form

extension SignInState {

    var isEmailValid: Bool {

        return Validation.isValid(email: email ?? "")

    }


    var isPasswordValid: Bool {

        return Validation.isValid(password: password ?? "")

    }


    var isFormValid: Bool {

        return isEmailValid && isPasswordValid

    }

}

Sign In Form

// SignInViewController:


func newState(state: AppState) {

    let state = state.signInForm


    serverErrorLabel.text = state.serverError

    serverErrorView.isHidden = state.serverError == nil


    emailField.text = state.email

    emailField.isEnabled = !state.isSigningIn

    emailErrorLabel.isHidden = !state.emailEditedOnce || state.isEmailValid


    passwordField.text = state.password

    passwordField.isEnabled = !state.isSigningIn

    passwordErrorLabel.isHidden = !state.passwordEditedOnce || state.isPasswordValid


    signInButton.isEnabled = state.isFormValid

    signInButton.isHidden = state.isSigningIn

    activityIndicator.isHidden = !state.isSigningIn

}

Sign In Form

enum SignInFormAction: Action {

    case request

    case handleSuccess

    case handleError(Error)


    case touchEmail

    case updateEmail(String)


    case touchPassword

    case updatePassword(String)

}

Sign In Form

    // Reducer:


    switch action {

    case .request:

        state.isSigningIn = true

    case .handleSuccess:

        state.isSigningIn = false

    case .handleError(let error):

        state.isSigningIn = false

        state.serverError = error.localizedDescription

    case .touchEmail:

        state.emailEditedOnce = true

    case .updateEmail(let email):

        state.email = email

    case .touchPassword:

        state.passwordEditedOnce = true

    case .updatePassword(let password):

        state.password = password

    }

Sign In Form

    // XCTestCase:


    func testInvalidEmailAndPassword() {

        let state = AppState()

        state.signInForm.email = "mark@facebook"

        state.signInForm.emailEditedOnce = true

        state.signInForm.password = "pas"

        state.signInForm.passwordEditedOnce = true

        controller.newState(state.signInForm)

        FBSnapshotVerifyView(window)

    }

Async

Action Creators

Action Creators

func createAction() {

    store.dispatch(StoplightAction.turnGreen)

}

Action Creators

func createActionsAsync() {

    store.dispatch(StoplightAction.turnYellow)


    let when = DispatchTime.now() + 4

    DispatchQueue.main.asyncAfter(deadline: when) {

        self.store.dispatch(StoplightAction.turnRed)

    }

}

Action Creators

func signIn(credential: Credential) {

    store.dispatch(SignInFormAction.request)

    api.signIn(with: credential) { [store] result in

        DispatchQueue.main.async {

            switch result {

            case .success(let user):

                store.dispatch(SignInFormAction.handleSuccess)

                store.dispatch(AuthenticationAction.signIn(user))

            case .error(let error):

                store.dispatch(SignInFormAction.handleError(error))

            }

        }

    }

}

Local Services

(core data)

App State?

Local Services

enum Managed {

    class Contact: NSManagedObject {

        var name: String!

        var phone: String!

    }

}

Local Services

struct Contact {

    let id: URL

    let name: String

    let phone: String


    init(contact: Managed.Contact) {

        id = contact.objectID.uriRepresentation()

        name = contact.name

        phone = contact.phone

    }

}

Local Services

enum ContactAction: Action {

    case load([Contact])

    case error(Error)

}

Local Services

func loadContacts() {

    let fetchRequest = NSFetchRequest<Managed.Contact>(entityName: "Contact")

    do {

        let contacts = try fetchRequest.execute()

        store.dispatch(ContactAction.load(contacts.map(Contact.init)))

    } catch {

        store.dispatch(ContactAction.error(error))

    }

}

Local Services

            contacts.count

            // => 10_000_000

One state

One state

  • Visible
  • Designed
  • Explicit
  • Type enforced

One state

  • Visible
  • Designed
  • Explicit
  • Type enforced

One state

  • Visible
  • Designed
  • Explicit
  • Type enforced

One state

  • Visible
  • Designed
  • Explicit
  • Type enforced

Type Enforced State

enum AppState {

    case unauthenticated(SignInForm)

    case authenticated(AuthenticatedState)

}


struct AuthenticatedState {

    let user: User

    let contacts: Loading<[Contact]>

}


enum Loading<T> {

    case loading

    case loaded(T)

    case error(Error)

}

     t

sta   e

Substate Selection

struct SignInViewModel {

    let email: String

    let password: String

    let serverError: String?


    let isSigningIn: Bool

    let isFormValid: Bool


    let hideServerError: Bool

    let hideEmailError: Bool

    let hidePasswordError: Bool


    init?(state: AppState) {

        // ...

    }

}

Substate Selection

    // SignInViewController:


    store.subscribe(self, selector: SignInViewModel.init)


    func newState(viewModel: SignInViewModel?) {

        guard let viewModel = viewModel else { return }


        serverErrorLabel.text = viewModel.serverError

        serverErrorView.hidden = viewModel.hideServerError


        // ...

    }

Animations

Animations

UIView.animateWithDuration(0.3) {

    self.emailErrorLabel.isHidden = hideEmailError

    self.passwordErrorLabel.isHidden = hidePasswordError

}

Animations

Drag and drop?
Uber loading screen?

Table & Collection Views

┌───────────────┐
│□    Apple     │
├───────────────┤
│□   Banana     │
├───────────────┤
│□    Pear      │
└───────────────┘


Table & Collection Views

┌───────────────┐           ┌───────────────┐
│□    Apple     │           │■    Apple     │
├───────────────┤           ├───────────────┤
│□   Banana     │           │□    Plum      │
├───────────────┤           ├───────────────┤
│□    Pear      │           │□   Banana     │
└───────────────┘           ├───────────────┤
                            │□    Pear      │
                            └───────────────┘

Table & Collection Views

┌───────────────┐           ┌───────────────┐
│□    Apple     │──────────▶│■    Apple     │
├───────────────┤           ├───────────────┤
│□   Banana     │     ─────▶│□    Plum      │
├───────────────┤           ├───────────────┤
│□    Pear      │           │□   Banana     │
└───────────────┘           ├───────────────┤
                            │□    Pear      │
                            └───────────────┘

Table & Collection Views

extension UITableView {

    func iDontKnowHowToFixThis() {

        reloadData()

    }

}

Table & Collection Views

github.com/jflinter/Dwifft

Table & Collection Views

github.com/willowtreeapps/tablediff

Table & Collection Views

// ViewController:


func newState(state: AppState) {

    let (diff, updates) = lastState.rows.tableDiff(state.rows)

    lastState = state


    let visible = Set(tableView.indexPathsForVisibleRows ?? [])

    for indexPath in updates.intersection(visible) {

        let cell = tableView.cellForRow(at: indexPath) as! CustomCell

        cell.update(state.rows[indexPath.row])

    }


    // automatically insert, delete, and move:

    tableView.applyDiff(diff)

}

Table & Collection Views

github.com/instagram/???

Navigation

Navigation

@IBAction func linkTapped() {

    let vc = NextViewController()

    navigationController?.pushViewController(vc, animated: true)

}

Navigation

    struct AppState {

        var route: [String]

    }



    let state = AppState(route: ["auth", "signIn"])

Navigation

        UIViewController.goTo(route: ["home", "movies", "kubo"])

Navigation

       One object                                       Many objects
 ◀───────────────────────────────────────────────────────────────────────▶
    Lots of knowledge                               Little knowledge each

Navigation

       One object                                       Many objects
 ◀─────────●─────────────────────────────────────────────────────────────▶
    Lots of knowledge                               Little knowledge each


func application(_ app: UIApplication, open url: URL,

    options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {

    // build the whole stack!

    return true

}

Navigation

       One object                                       Many objects
 ◀───────────────────────────────────────────────────────────●───────────▶
    Lots of knowledge                               Little knowledge each


protocol Routable {

    func changeRouteSegment(from: ..., to: ..., completion: ...) -> Routable


    func pushRouteSegment(identifier: ..., completion: ...) -> Routable


    func popRouteSegment(identifier: ... , completionI: ...)

}

When you call something a controller... nothing is out of scope, since its purpose is to control things.
— Soroush Khanlou

http://khanlou.com/2015/10/coordinators-redux/

protocol SignInHandler: class {

    func signIn(credential: Credential)

    func forgotPassword()

}


final class SignInViewController: UIViewController {

    weak var handler: SignInHandler!


    @IBAction func signInButtonPressed(sender: AnyObject) {

        let credential = Credential()

        handler.signIn(credential: credential)

    }


    @IBAction func forgotPasswordButtonPressed(sender: AnyObject) {

        handler.forgotPassword()

    }

}

App Coordinators

  • Instantiate view controllers
  • Handle user actions
  • Perform navigation

App Coordinators

  • Instantiate view controllers
  • Handle user actions
  • Perform navigation

App Coordinators

  • Instantiate view controllers
  • Handle user actions
  • Perform navigation

http://khanlou.com/2015/10/coordinators-redux/

http://khanlou.com/2015/10/coordinators-redux/

http://khanlou.com/2015/10/coordinators-redux/

http://khanlou.com/2015/10/coordinators-redux/

App coordinators instantiate view controllers

App coordinators instantiate view controllers

& subscribe them to the store

App coordinators handle user actions

App coordinators handle user actions

& dispatch data actions to the store

App coordinators perform navigation

App coordinators perform navigation

via the app state's route

Navigation


 ◀──────────────────────────── Coordinators ─────────────────────────────▶

Coordinators














                           ┌───────────┐
                           │           │
                           │           │
                           │           │
                           └Controller─┘








         ┌────┬─────────┐
         │####│~~!@$)_+_│
         │####│+[][;;'; │
         ├────┴──┬──────┤
         │*******│      │
         ├──┬────┴──────┤
         │  │~~~~~~~~~~~│
         ├──┴───────┬───┤
         │          └───┤
         └─────View─────┘








                           ┌───────────┐
                           │┌┐┌┐┌┐┌┐┌┐ │
                           ││└┘└┘└┘└┘│ │
                           │◀────────┘ │
                           └───Store───┘













                                                         ┌──────────────┐
                                                         │   Database   │
                                                         ├ ─ ─ ─ ─ ─ ─ ─│
                                                         │     API      │
                                                         ├ ─ ─ ─ ─ ─ ─ ─│
                                                         │    Cache     │
                                                         ├ ─ ─ ─ ─ ─ ─ ─│
                                                         │ Notification │
                                                         ├ ─ ─ ─ ─ ─ ─ ─│
                                                         └────Model─────┘








                                          ┌───────────┐
                                          │           │
                                          │           │
                                          │           │
                                          │           │
                                          │           │
                                          │           │
                                          │           │
                                          │           │
                                          └Coordinator┘









                                                     ◀────▶

                                                     ◀────▶

                                                     ◀────▶

                                                     ◀────▶














                              │
                              │
                              ▼

















                      ◀──────



















                       ─────▶

















                                      ─────▶











                                      ◀─────







Coordinators

github.com/willowtreeapps/cordux

Why?

  • Better state management
  • Lightweight view controllers

Why?

  • Better state management
    • Visible
  • Lightweight view controllers

Why?

  • Better state management
    • Visible
    • Designed
  • Lightweight view controllers

Why?

  • Better state management
    • Visible
    • Designed
    • Explicit
  • Lightweight view controllers

Why?

  • Better state management
    • Visible
    • Designed
    • Explicit
    • Type system enforced
  • Lightweight view controllers

Why?

  • Better state management
    • Visible
    • Designed
    • Explicit
    • Type system enforced
    • Testable transitions
  • Lightweight view controllers

Why?

  • Better state management
    • Visible
    • Designed
    • Explicit
    • Type system enforced
    • Testable transitions
  • Lightweight view controllers

Resources

Flux https://facebook.github.io/flux/
Redux https://github.com/reactjs/redux
ReSwift https://github.com/ReSwift/ReSwift
Coordinators http://khanlou.com/2015/10/coordinators-redux/
Cordux https://github.com/willowtreeapps/cordux
Dwifft https://github.com/jflinter/Dwifft
TableDiff https://github.com/willowtreeapps/tablediff
FBSnapshotTestCase https://github.com/facebook/ios-snapshot-test-case

fin