Post on 16-Apr-2017
UNIDIRECTIONAL DATA FLOWWITH REACTOR
WHAT IS THE STATE OF YOUR APP?
WHERE DOES STATE LIVE?
WHAT DOES STATE LOOK LIKE?
STATEstruct Player: State { var name: String var level: Int}
STATE COMPOSITIONstruct RPGState { var players: Player var monsters: Monsters
// ...}
CHANGING STATEDATA FLOW
▸ Delegates▸ Target-Actions▸ Completion Blocks▸ Notification Center
▸ KVO▸ Segues▸ didSet
▸ NSFetchedResultsController
WHAT DIRECTION IS THE DATA FLOWING?
SPOT THE BUG!class ViewController: UIViewController { @IBOutlet var titleLabel: UILabel!
var name: String? { didSet { titleLabel.text = name } }}
SPOT THE NEW BUG!class ViewController: UIViewController { @IBOutlet var titleLabel: UILabel?
var name: String? { didSet { titleLabel?.text = name } }}
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() }}
PROBLEM: UNDEFINED DATA
FLOW
SINGLE SOURCE OF TRUTH™
(STATE) → VIEWUI AS A PURE FUNCTION OF STATE
REDRAW VIEW ON EVERY STATE CHANGE?!?!?
REACT✨ VIRTUAL DOM ✨
!UIKIT
(STATE) → VOIDWITH SIDE EFFECTS
extension ViewController { func update(with state: State) { nameLabel.text = state.name }}
UNIDIRECTIONAL DATA FLOWALL UPDATES FLOW IN SAME DIRECTION
JARSEN/REACTOR
CORE▸ Holds the Single Source of Truth™▸ Only the Core can change the state
▸ Notifies all subscribers with state changes
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 }}
UPDATING STATEstruct Increment: Event {}
extension ViewController { @IBAction func didPressIncrement() { core.fire(event: Increment()) }}
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)) } }}
ASYNC EVENTSextension ViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) core.add(subscriber: self) core.fire(command: GetUsers()) }}
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 } }}
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) }}
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 } }}
HOT RELOADINGUSING MARSHAL + KZFILEWATCHER
OPTIMISTIC NETWORK RESULTS
TIME TRAVEL
STATE RESTORATION
PERFORMANCE CONCERNS
Don't like the API? ☹Don't like 3rd Party? "
ROLL YOUR OWN !It's a straightforward, not-too-novel pattern.
NAVIGATION STATE / ROUTING
REACTOR RESOURCES▸ React Native, Native
▸ Optimistic Networking with Reactor
OTHER RESOURCES▸ A composable pattern for pure state machines with effects by Andy
Matuschak▸ Unidirectional Data Flow in Swift by Benjamin Encz
▸ Elmification of Swift▸ MCV-N Swift Talk by Marcus Zarra
▸ Backend-driven Native UIs by John Sundell