Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Global state architecture in Swift

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →

Global state architecture in Swift

Slides of my presentation at NSCoders Madrid.

Avatar for Ricardo Sánchez

Ricardo Sánchez

October 06, 2016

Other Decks in Programming

Transcript

  1. MVC

  2. MVC

  3. MVC

  4. MVC ▸ Old (1979) ▸ Vaguely defined ▸ Won't tell

    you where to put all parts of a modern application
  5. MVP & MVVM ▸ Both try to decouple view and

    controller (presenter or view model) ▸ MVVM uses bindings, MVP interfaces ▸ As with MVC, they just tell you about the "view" part of your app
  6. VIPER ▸ Complex, has lots of parts ▸ A bunch

    of boilerplate code ▸ Some Interactors end up just passing values ▸ Where do I keep the state? Where is the truth? ▸ How do I pass data between modules?
  7. REDUX ▸ Facebook's Javascript framework ▸ Based of Flux (another

    Facebook framework) ▸ Used together with React for the UI ▸ Reduces complexity derived from mixing Mentos and Coke (mutations and asynchronicity) ▸ One global state of the application
  8. REDUX PRINCIPLES ▸ Single source of truth ▸ State is

    read-only ▸ Changes to state are made with pure functions
  9. GLOBAL STATE ISN'T ALWAYS BAD ▸ It's all about control

    ▸ Immutability / Functional Programming ▸ Actions are atomic and simple state changes ▸ By having a global state everything else will be (almost) stateless ▸ Only one state to debug
  10. RESWIFT struct AppState: StateType { var counter: Int = 0

    } ... struct CounterActionIncrease: Action {} struct CounterActionDecrease: Action {}
  11. RESWIFT struct CounterReducer: Reducer { func handleAction(action: Action, state: AppState?)

    -> AppState { var state = state ?? AppState() switch action { case _ as CounterActionIncrease: state.counter += 1 case _ as CounterActionDecrease: state.counter -= 1 default: break } return state } }
  12. RESWIFT ... override func viewWillAppear(animated: Bool) { mainStore.subscribe(self) } func

    newState(state: AppState) { counterLabel.text = "\(state.counter)" } @IBAction func increaseButtonTapped(sender: UIButton) { mainStore.dispatch( CounterActionIncrease() ) } ...
  13. ACTION CREATORS func doSomething () -> Store<AppState>.ActionCreator { return {

    state, store in apiClient.doSomething() { store.dispatch(MyAction()) } } }
  14. ASYNC ACTION CREATORS func authenticateUser() -> AsyncActionCreator { return {

    maybeState, store, callback in guard let state = maybeState as? AppState else { return } if let swifter = state.twitterAPIState.swifter { callback(self.setTwitterClient(swifter)) } else { self.twitterClient.login().start { event in switch event { case let .Next(swifter): callback(self.setTwitterClient(swifter)) default: print("oh") } } } } } func setTwitterClient(swifter: Swifter) -> ActionCreator { return { _ in return SetTwitterClient(swifter) } }
  15. RESWIFT PROS ▸ Simple and small framework ▸ Actively contributed

    to ▸ Can get ideas for extensions from Redux
  16. RESWIFT CONS ▸ UIKit doesn't make it easy (navigation &

    diffing) ▸ Async actions unclear and not very well documented ▸ The idea is so simple you don't need a framework (or you can make your own)
  17. REQUIREMENTS ▸ Follows redux's three principles ▸ Uses Rx to

    simplify asynchronous actions ▸ Also uses Rx to subscribe to the store ▸ Won't handle navigation, errors or loading states
  18. CACHOPO ASYNC ACTIONS struct SetSearchResults:Action { let artists:[Artist] } func

    searchArtists(searchString:String) -> Reader<Enviroment, Observable<Action>> { return Reader<Enviroment, Observable<Action>>{ state, enviroment in return enviroment.service.load(Artist.matching(searchString)) .map { SetSearchResults(artists: $0) } } }
  19. READER MONAD public class Reader<E, A> { let g: (E)

    -> A init(_ g:(E) -> A) { self.g = g } func run(e: E) -> A { return g(e) } func map<B>(f:(A) -> B) -> Reader<E, B> { return Reader<E, B>{ e in f(self.g(e)) } } func flatMap<B>(f:(A) -> Reader<E, B>) -> Reader<E, B> { return Reader<E, B>{ e in f(self.g(e)).g(e) } } }
  20. WHAT'S MISSING ▸ Lots of testing (and lots of unit

    tests) ▸ Tool for watching/modifying the state in real time ▸ Declarative UI framework integration ▸ Maybe uses lenses to simplify reducers?