UNIDIRECTIONAL
DATA FLOW
WHAT IS THE STATE OF
YOUR APP?
WHERE DOES STATE LIVE?
WHAT DOES STATE LOOK
LIKE?
STATE
struct Player {
var name: String
var characterClass: CharacterClass
var maxHP: Int
var currentHP: Int
}
STATE COMPOSITION
struct RPGState {
var players: Player
var monsters: Monsters
// ...
}
(STATE) -> VIEW
UI 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 State
class 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 STATE
struct Increment: Event {}
extension ViewController {
@IBAction func didPressIncrement() {
reactor.perform(event: Increment())
}
}
ASYNC EVENTS
struct 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 EVENTS
extension 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

Unidirectional Data Flow in Swift