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

2 years of Redux in iOS. Lessons learned

DAloG
July 15, 2017

2 years of Redux in iOS. Lessons learned

DAloG

July 15, 2017
Tweet

More Decks by DAloG

Other Decks in Programming

Transcript

  1. ALEXEY DEMEDECKIY @DALOOG > 7 years of iOS development >

    Early adopter > Strong typed person > Semantic oriented programmer
  2. DATA-DRIVEN ARCHITECTURES > Flux > Redux > Elm > MobX

    > Cycle.js ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  3. > Single source of truth > Single place of change

    > Single way to update ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  4. ...debugging is twice as hard as writing a program in

    the first place. — Brian Kernighan (1985) ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  5. LESSONS: 1. Data-driven VC 2. Correct ViewModels 3. Data-driven vs

    event-driven 4. Managed side effects 5. High level testing ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  6. TRADITIONAL MVC class ViewController: UIViewController { var modelID: String? var

    api: APIClient? var database: DatabaseWrapper? } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  7. TRADITIONAL MVC > intermediate state > hard to track changes

    > fragile UI ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  8. REACTIVE MVVM class ViewModel { var name: Observable<String> var age:

    Observable<String> func birthday() {} func mariage() {} } class ViewController: UIViewController { var viewModel: Observable<ViewModel> } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  9. REACTIVE MVVM > transient states > sharing view models >

    reactive paradigm ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  10. DATA-DRIVEN VC class ViewController: UIViewController { struct ViewModel { let

    name: String let age: String let birthday: () -> () let marry: () -> () } var viewModel: ViewModel { didSet { setNeedsLayout() } } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  11. LOGGING IN struct ViewModel { let isLoggedIn: Bool let isLoading:

    Bool let login: (Username, Password) -> () let logout: () -> () } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  12. LOGGING IN enum ViewModel { case loginForm(login: (Username, Password) ->

    ()) case loading case loggedIn(logout: () -> ()) } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  13. REAL LIFE EXAMPLE public enum Props { case noPlayer, player(Player),

    pictureInPicture public struct Player { public var playlist: Playlist? public struct Playlist { public var next: Action<Void>? public var prev: Action<Void>? } public var item = Item.nonplayable("") public enum Item { case playable(Controls) case nonplayable(String) } } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  14. LOGGING IN struct UIProps { let loginButtonAction: () -> ()

    let loginButtonEnabled: Bool let spinnerHidden: Bool let logoutButtonAction: () -> () let logoutButtonEnabled: Bool init(viewModel: ViewModel) { ... } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  15. REAL LIFE EXAMPLE struct UIProps { var loading: Bool ...

    init(props: Props) { loading = { guard case .player(let player) = props else { return false } guard case .playable(let props) = player.item else { return false } return props.loading }() } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  16. BEST OF BOTH WORLDS > Correctness of state > Simplicity

    of rendering > Tesatbility ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  17. Lesson #2: CORRECTNESS OF VALUES SHOULD BE ENFORCED WITH TYPES

    ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  18. EVENTS ARE FRAGILE > Order is crucial > Context is

    important > Errors are cumulative ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  19. DATA IS BETTER THAN EVENT > No order constraints >

    You can miss some data > Eventually ok ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  20. REAL LIVE EXAMPLE public struct AVPlayerProps { public let url:

    URL public let isPlaying: Bool public let isMuted: Bool public let newTime: CMTime? public let callbacks: PlayerObserver.Callbacks public let didPlayToTime: Action<CMTime> } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  21. EVENT SOURCES > external systems > side effects > user

    input ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  22. DATA SOURCE class Application<Model, Event> { var model: Observable<Model> init(from

    model: Model) { ... } func apply(event: Event) { ... } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  23. SINGLETONS class APIClient { static let shared = APIClient() private

    init() { ... } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  24. SINGLETONS > Unclear usage > Shared state > Miss of

    context ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  25. DI protocol APIClient { ... } class Service { var

    apiClient: APIClient } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  26. DI > Hard to manage > Transient dependencies > API

    breakage ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  27. DI func login(username: Username, password: Password) { self.apiClient.get( path: "/session",

    params: ["useraname": useraname, "password": password]) { error, value in if let error = error { return self.showAlert(error) } return self.pushUserPage(value!) } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  28. EFFECTS enum Effect { case none case get(Path, Params, (Error?,

    Value?) -> Effect) case showAlert(Error) case pushUserPage(Value) } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  29. EFFECTS func login(username: Username, password: Password) -> Effect { let

    params = ["username": username, "password": password] return .get("/session", params) { error, value in if let error = error { return .showError(error) } if let value = value { return .pushUserPage(value) } return .none } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  30. EFFECTS class Application<Model, Event> { var model: Observable<Model> init(from model:

    Model) { ... } func apply(event: Event) { ... } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  31. UNIT TESTS IN DATA-DRIVEN > Easy to start > Low

    support cost > Show nothing ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  32. USUAL INTEGRATION TESTS > Hard to write > Requires constant

    attention > Covers only critical sections ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  33. DATA-DRIVEN INTEGRATION TESTS class Application<Model, Event, Effect> { var tracer:

    Tracer<Model, Event, Effect> var model: Model { didSet { tracer.record(model: model) } } func apply(event: Event) { tracer.record(event) } func perform(effect: Effect) { tracer.record(effect) } } ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  34. RESULTS > events.json - 3MB > model.json - 35MB >

    effects.json - 4MB ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  35. VERIFICATION TESTING > Record real usage scenarios > Verify consistency

    > Discover side effects ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  36. 1. Data-driven VC 2. Correct ViewModels 3. Data-driven vs event-driven

    4. Managed side effects 5. High level testing ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  37. USEFUL LINKS > Idiomatic Redux > Video Player Controls >

    Video Renderer > Elm ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  38. COMMUNITY EFFORTS > Swift Frameworks by @inamiy > Statements, messages

    and reducers by @cocoawithlove > Reducers by @chriseidhof ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  39. KATANA > Virtual UI > Data-driven layout > Strong foundation

    ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  40. PORTAL BY @GUIDOMB > Elm inspired > Managed side effects

    > Data-driven layout ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF
  41. ADVANCED TOPICS > Cross-platform core (JSCore) > Tooling (Redux Dev

    Tools) > Adopting in teams ALEXEY DEMEDECKIY (@DALOOG) FOR #MOCONF