Upload
jason-larsen
View
184
Download
0
Embed Size (px)
Citation preview
UNIDIRECTIONAL DATA FLOW
WHAT IS THE STATE OF YOUR APP?
WHERE DOES STATE LIVE?
WHAT DOES STATE LOOK LIKE?
STATEstruct Player { var name: String var characterClass: CharacterClass var maxHP: Int var currentHP: Int}
STATE COMPOSITION
struct RPGState { var players: Player var monsters: Monsters
// ...}
(STATE) -> VIEWUI AS A PURE FUNCTION OF STATE
REDRAW VIEW ON EVERY STATE CHANGE?!?!?
REACT✨ VIRTUAL DOM ✨
!UIKIT
(STATE) -> VOID(WITH SIDE EFFECTS)
extension ViewController { func update(with state: State) { nameLabel.text = state.name }}
CHANGING STATE
DATA FLOW
> Delegates> Target-Actions
> Completion Blocks> Notifications
> Segues> didSet
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 DANCE
class ViewController: UIViewController { @IBOutlet var titleLabel: UILabel?
var name: String? { didSet { configureView() } }
func configureView() { titleLabel?.text = name }
override func viewDidLoad() { super.viewDidLoad() configure() }}
SINGLE SOURCE OF TRUTH™
JARSEN/REACTOR
REACTOR
> Holds the Single Source of Truth™> Only the reactor can change the state
> Notifies all subscribers with state changes
Observing Stateclass ViewController: Reactor.Subscriber { var reactor = App.sharedReactor
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) reactor.add(subscriber: self) }
override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) reactor.remove(subscriber: self) }
func update(with state: State) { nameLabel.text = state.name }}
UPDATING STATEstruct Increment: Event {}
extension ViewController { @IBAction func didPressIncrement() { reactor.perform(event: Increment()) }}
ASYNC EVENTSstruct Update<T>: Event { var value: T}
class UserService { // this type of function is also knows as an `EventEmitter` func getUsers(state: State, reactor: Reactor<State>) -> Event? { myNetworkThing.get("users") { json in // transform to user object, using Marshal, of course reactor.perform(event: Update(value: users)) } }}
ASYNC EVENTSextension ViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) reactor.add(subscriber: self) reactor.perform(event: userService.getUsers) }}
HOW DO EVENTS UPDATE STATE?extension State: StateType { mutating func handle(event: Event) { switch event { case _ as LevelUp: player.level += 1 default: break } // also call `handle` on any substates }}
PERFORMANCE CONCERNS
ROLL YOUR OWN
> Don't like the API?
> Don't like 3rd Party? !
It's a straightforward pattern.
NAVIGATION STATE / ROUTING
ENDLESS POSSIBILITIES
MIDDLEWARE
OPTIMISTIC NETWORK RESULTS
TIME TRAVEL
STATE RESTORATION
HOT RELOADINGUSING MARSHAL + KZFILEWATCHER
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