41
UNIDIRECTIONAL DATA FLOW WITH REACTOR

Unidirectional Data Flow with Reactor

Embed Size (px)

Citation preview

Page 1: Unidirectional Data Flow with Reactor

UNIDIRECTIONAL DATA FLOWWITH REACTOR

Page 2: Unidirectional Data Flow with Reactor

WHAT IS THE STATE OF YOUR APP?

Page 3: Unidirectional Data Flow with Reactor

WHERE DOES STATE LIVE?

Page 4: Unidirectional Data Flow with Reactor

WHAT DOES STATE LOOK LIKE?

Page 5: Unidirectional Data Flow with Reactor

STATEstruct Player: State { var name: String var level: Int}

Page 6: Unidirectional Data Flow with Reactor

STATE COMPOSITIONstruct RPGState { var players: Player var monsters: Monsters

// ...}

Page 7: Unidirectional Data Flow with Reactor

CHANGING STATEDATA FLOW

Page 8: Unidirectional Data Flow with Reactor

▸ Delegates▸ Target-Actions▸ Completion Blocks▸ Notification Center

▸ KVO▸ Segues▸ didSet

▸ NSFetchedResultsController

Page 9: Unidirectional Data Flow with Reactor

WHAT DIRECTION IS THE DATA FLOWING?

Page 10: Unidirectional Data Flow with Reactor

SPOT THE BUG!class ViewController: UIViewController { @IBOutlet var titleLabel: UILabel!

var name: String? { didSet { titleLabel.text = name } }}

Page 11: Unidirectional Data Flow with Reactor

SPOT THE NEW BUG!class ViewController: UIViewController { @IBOutlet var titleLabel: UILabel?

var name: String? { didSet { titleLabel?.text = name } }}

Page 12: Unidirectional Data Flow with Reactor

THE IMPLICITLY UNWRAPPED OPTIONAL DANCEclass ViewController: UIViewController { @IBOutlet var titleLabel: UILabel?

var name: String? { didSet { configureView() } }

func configureView() { titleLabel?.text = name }

override func viewDidLoad() { super.viewDidLoad() configure() }}

Page 13: Unidirectional Data Flow with Reactor

PROBLEM: UNDEFINED DATA

FLOW

Page 14: Unidirectional Data Flow with Reactor

SINGLE SOURCE OF TRUTH™

Page 15: Unidirectional Data Flow with Reactor

(STATE) → VIEWUI AS A PURE FUNCTION OF STATE

Page 16: Unidirectional Data Flow with Reactor

REDRAW VIEW ON EVERY STATE CHANGE?!?!?

Page 17: Unidirectional Data Flow with Reactor

REACT✨ VIRTUAL DOM ✨

Page 18: Unidirectional Data Flow with Reactor

!UIKIT

Page 19: Unidirectional Data Flow with Reactor

(STATE) → VOIDWITH SIDE EFFECTS

Page 20: Unidirectional Data Flow with Reactor

extension ViewController { func update(with state: State) { nameLabel.text = state.name }}

Page 21: Unidirectional Data Flow with Reactor

UNIDIRECTIONAL DATA FLOWALL UPDATES FLOW IN SAME DIRECTION

Page 22: Unidirectional Data Flow with Reactor

JARSEN/REACTOR

Page 23: Unidirectional Data Flow with Reactor
Page 24: Unidirectional Data Flow with Reactor

CORE▸ Holds the Single Source of Truth™▸ Only the Core can change the state

▸ Notifies all subscribers with state changes

Page 25: Unidirectional Data Flow with Reactor

OBSERVING STATEclass ViewController: core.Subscriber { var core = App.sharedCore @IBOutlet var nameLabel: UILabel!

override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) core.add(subscriber: self) }

override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) core.remove(subscriber: self) }

func update(with state: State) { nameLabel.text = state.name }}

Page 26: Unidirectional Data Flow with Reactor

UPDATING STATEstruct Increment: Event {}

extension ViewController { @IBAction func didPressIncrement() { core.fire(event: Increment()) }}

Page 27: Unidirectional Data Flow with Reactor

ASYNC EVENTSstruct Update<T>: Event { var value: T}

struct GetUsers: Command { func execute(state: State, core: Core<State>) { myNetworkThing.get("users") { json in // transform to user object, using Marshal, of course core.fire(event: Update(value: users)) } }}

Page 28: Unidirectional Data Flow with Reactor

ASYNC EVENTSextension ViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) core.add(subscriber: self) core.fire(command: GetUsers()) }}

Page 29: Unidirectional Data Flow with Reactor

HOW DO EVENTS UPDATE STATE?struct Player: State { var name: String var level: Int

mutating func react(to event: Event) { switch event { case let _ as LevelUp: level += 1 default: break } }}

Page 30: Unidirectional Data Flow with Reactor

HOW DO EVENTS UPDATE COMPOSED STATES?struct RPGState: State { var player: Player var monsters: Monsters

mutating func react(to event: Event) { player.react(to: event) monsters.react(to: event) }}

Page 31: Unidirectional Data Flow with Reactor

MIDDLEWAREGood for side effects like logging, analytics, displaying errors...

struct LoggingMiddleware: Middleware { func process(event: Event, state: State) { switch event { case _ as LevelUp: print("Leveled Up!") default: break } }}

Page 32: Unidirectional Data Flow with Reactor

HOT RELOADINGUSING MARSHAL + KZFILEWATCHER

Page 33: Unidirectional Data Flow with Reactor
Page 34: Unidirectional Data Flow with Reactor

OPTIMISTIC NETWORK RESULTS

Page 35: Unidirectional Data Flow with Reactor

TIME TRAVEL

Page 36: Unidirectional Data Flow with Reactor

STATE RESTORATION

Page 37: Unidirectional Data Flow with Reactor

PERFORMANCE CONCERNS

Page 38: Unidirectional Data Flow with Reactor

Don't like the API? ☹Don't like 3rd Party? "

ROLL YOUR OWN !It's a straightforward, not-too-novel pattern.

Page 39: Unidirectional Data Flow with Reactor

NAVIGATION STATE / ROUTING