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

Exploring Stateless UIs in Swift

Exploring Stateless UIs in Swift

Taking inspiration from architectural patterns like Flux and Redux, this session is an exploration of one-way data flow principles in Swift.

It's hard to resist the influence of Apple's Cocoa MVC architecture on our apps. We've all seen or written view controllers with thousands of lines of code that buckle under the weight of their responsibilities. They're difficult to test, modify, and maintain.

By thinking differently about how we handle application state over time, we've reduced complexity and improved extensibility of production code. And you don't need to buy into a specific architecture–you can benefit from using one-way data flow principles in your apps right now.

Sam Kirchmeier

April 23, 2016
Tweet

More Decks by Sam Kirchmeier

Other Decks in Programming

Transcript

  1. EXPLORING
    STATELESS UIs
    in SWIFT
    Adam May & Sam Kirchmeier

    View full-size slide

  2. tchacknight.com

    View full-size slide

  3. Define application state
    TOPICS

    View full-size slide

  4. Define application state
    Describe a stateless architecture for iOS
    TOPICS

    View full-size slide

  5. Define application state
    Describe a stateless architecture for iOS
    Show examples
    TOPICS

    View full-size slide

  6. Define application state
    Describe a stateless architecture for iOS
    Show examples
    Discuss challenges and future work
    TOPICS

    View full-size slide

  7. STATELESS UIs

    View full-size slide

  8. WHAT IS STATE?

    View full-size slide

  9. The value
    of an identity
    at a moment in time
    WHAT IS STATE?
    Source: http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey

    View full-size slide

  10. The value
    of an identity
    at a moment in time
    WHAT IS STATE?

    View full-size slide

  11. The value
    of an identity
    at a moment in time
    WHAT IS STATE?

    View full-size slide

  12. The value
    of an identity
    at a moment in time
    WHAT IS STATE?

    View full-size slide

  13. The value
    of an identity
    at a moment in time
    WHAT IS STATE?

    View full-size slide

  14. STATE IN SWIFT

    View full-size slide

  15. STATE IN SWIFT
    var str = "Hello, playground"

    View full-size slide

  16. Identity
    Value
    Time
    STATE IN SWIFT
    var str = "Hello, playground"

    View full-size slide

  17. Identity
    Value
    Time
    STATE IN SWIFT
    var str = "Hello, playground"

    View full-size slide

  18. Identity
    Value
    Time
    STATE IN SWIFT
    var str = "Hello, playground"

    View full-size slide

  19. HIDDEN COMPLEXITY
    var str = "Hello, playground"

    View full-size slide

  20. Assume str is global
    HIDDEN COMPLEXITY
    var str = "Hello, playground"

    View full-size slide

  21. Assume str is global
    It’s mutable from anywhere
    HIDDEN COMPLEXITY
    var str = "Hello, playground"

    View full-size slide

  22. ENCAPSULATION
    class NeatlyEncapsulated {
    private
    }
    var str = "Hello, playground"

    View full-size slide

  23. Mutable via private methods
    ENCAPSULATION
    var str = "Hello, playground"
    class NeatlyEncapsulated {
    private
    }

    View full-size slide

  24. Mutable via private methods
    Indirectly mutable via public methods
    ENCAPSULATION
    var str = "Hello, playground"
    class NeatlyEncapsulated {
    private
    }

    View full-size slide

  25. Mutable via private methods
    Indirectly mutable via public methods
    Same number of moving parts
    ENCAPSULATION
    var str = "Hello, playground"
    class NeatlyEncapsulated {
    private
    }

    View full-size slide

  26. STATE, THE PROBLEM

    View full-size slide

  27. State breeds complexity
    STATE, THE PROBLEM

    View full-size slide

  28. State breeds complexity
    Unexpected mutations
    STATE, THE PROBLEM

    View full-size slide

  29. State breeds complexity
    Unexpected mutations
    Concurrency and parallelism
    STATE, THE PROBLEM

    View full-size slide

  30. State breeds complexity
    Unexpected mutations
    Concurrency and parallelism
    Testing
    STATE, THE PROBLEM

    View full-size slide

  31. WHAT DOES IT MEAN TO BE STATELESS?

    View full-size slide

  32. Does not depend on variables
    WHAT DOES IT MEAN TO BE STATELESS?

    View full-size slide

  33. Does not depend on variables
    Same inputs same outputs
    WHAT DOES IT MEAN TO BE STATELESS?

    View full-size slide

  34. Does not depend on variables
    Same inputs same outputs
    No side effects
    WHAT DOES IT MEAN TO BE STATELESS?

    View full-size slide

  35. iOS IS INHERENTLY STATEFUL

    View full-size slide

  36. Persistence
    iOS IS INHERENTLY STATEFUL

    View full-size slide

  37. Persistence
    Networking
    iOS IS INHERENTLY STATEFUL

    View full-size slide

  38. Persistence
    Networking
    Memory
    iOS IS INHERENTLY STATEFUL

    View full-size slide

  39. Persistence
    Networking
    Memory
    UI
    iOS IS INHERENTLY STATEFUL

    View full-size slide

  40. Model
    View
    Controller
    update update
    notify
    action
    Source: https://developer.apple.com/library/ios/documentation/General/
    Conceptual/DevPedia-CocoaCore/MVC.html

    View full-size slide

  41. Model
    View
    Controller
    update update
    notify
    action
    state

    View full-size slide

  42. Model
    View
    Controller
    update update
    notify
    action
    state state

    View full-size slide

  43. Model
    View
    Controller
    update update
    notify
    action
    state
    state
    state

    View full-size slide

  44. HOW CAN WE DEAL WITH THIS?

    View full-size slide

  45. Isolate or eliminate state (vars)
    HOW CAN WE DEAL WITH THIS?

    View full-size slide

  46. Isolate or eliminate state (vars)
    Rely on values whenever possible
    HOW CAN WE DEAL WITH THIS?

    View full-size slide

  47. SWIFT TO THE RESCUE

    View full-size slide

  48. Constants
    SWIFT TO THE RESCUE

    View full-size slide

  49. Constants
    Value types
    SWIFT TO THE RESCUE

    View full-size slide

  50. CONSTANTS
    let str = "Hello, playground"
    str = "Goodbye!" // Compiler error!

    View full-size slide

  51. Code that relies on constants is stateless
    CONSTANTS
    let str = "Hello, playground"
    str = "Goodbye!" // Compiler error!

    View full-size slide

  52. Int, Float, Double
    VALUE TYPES

    View full-size slide

  53. Int, Float, Double
    String
    VALUE TYPES

    View full-size slide

  54. Int, Float, Double
    String
    Array, Dictionary, Set
    VALUE TYPES

    View full-size slide

  55. Int, Float, Double
    String
    Array, Dictionary, Set
    Struct, Enum
    VALUE TYPES

    View full-size slide

  56. Int, Float, Double
    String
    Array, Dictionary, Set
    Struct, Enum
    Code that relies on value types is stateless
    VALUE TYPES

    View full-size slide

  57. VALUE TYPES
    class Product {
    var price: String
    }

    View full-size slide

  58. VALUE TYPES
    class Product {
    var price: String
    }
    var credenza = Product(price: "$11.00")

    View full-size slide

  59. VALUE TYPES
    class Product {
    var price: String
    }
    var credenza = Product(price: "$11.00")
    var niceCredenza = credenza
    niceCredenza.price = "$12.00"

    View full-size slide

  60. VALUE TYPES
    class Product {
    var price: String
    }
    var credenza = Product(price: "$11.00")
    var niceCredenza = credenza
    niceCredenza.price = "$12.00"
    credenza.price // “$12.00”

    View full-size slide

  61. VALUE TYPES
    struct Product {
    var price: String
    }
    class Product {
    var price: String
    }

    View full-size slide

  62. VALUE TYPES
    struct Product {
    var price: String
    }
    var credenza = Product(price: "$11.00")
    var niceCredenza = credenza
    niceCredenza.price = "$12.00"
    credenza.price // "$11.00"

    View full-size slide

  63. WE NEED A NEW PARADIGM

    View full-size slide

  64. Eliminates and isolates state
    WE NEED A NEW PARADIGM

    View full-size slide

  65. Eliminates and isolates state
    Takes advantage of Swift’s constants
    WE NEED A NEW PARADIGM

    View full-size slide

  66. Eliminates and isolates state
    Takes advantage of Swift’s constants
    Takes advantage of Swift’s value types
    WE NEED A NEW PARADIGM

    View full-size slide

  67. View
    Store
    Dispatcher

    View full-size slide

  68. View
    Store
    Dispatcher

    View full-size slide

  69. Model
    Controller View
    updates
    manipulates
    User
    uses shows
    Source: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

    View full-size slide

  70. View
    Store
    Dispatcher

    View full-size slide

  71. View
    Store
    Stores the current state
    state

    View full-size slide

  72. View
    Store
    Stores the current state
    Produces the next state
    state

    View full-size slide

  73. View
    Store
    Stores the current state
    Produces the next state
    Is a class
    state

    View full-size slide

  74. View
    Store
    Dispatcher
    state

    View full-size slide

  75. View
    Store
    state

    View full-size slide

  76. View
    Store
    state Is app state

    View full-size slide

  77. View
    Store
    state Is app state (everything)

    View full-size slide

  78. View
    Store
    state Is app state (everything)
    Is a moment in time

    View full-size slide

  79. View
    Store
    state Is app state (everything)
    Is a moment in time
    Is immutable

    View full-size slide

  80. View
    Store
    state Is app state (everything)
    Is a moment in time
    Is immutable
    Is a value

    View full-size slide

  81. View
    Store
    Dispatcher
    state

    View full-size slide

  82. View
    Store
    Dispatcher
    state
    state

    View full-size slide

  83. View
    Receives and renders state

    View full-size slide

  84. View
    Receives and renders state
    Can’t mutate state

    View full-size slide

  85. View
    Receives and renders state
    Can’t mutate state
    Triggers actions

    View full-size slide

  86. View
    Receives and renders state
    Can’t mutate state
    Triggers actions
    Is a view controller or view

    View full-size slide

  87. View
    Receives and renders state
    Can’t mutate state
    Triggers actions
    Is a view controller or view
    Is a class

    View full-size slide

  88. View
    Store
    Dispatcher
    state
    state

    View full-size slide

  89. View
    Store
    Dispatcher
    state
    state
    action

    View full-size slide

  90. View
    action Represents an interaction

    View full-size slide

  91. View
    action Represents an interaction
    Is not state

    View full-size slide

  92. View
    action Represents an interaction
    Is not state
    Includes an action type

    View full-size slide

  93. View
    action Represents an interaction
    Is not state
    Includes an action type
    Includes relevant data

    View full-size slide

  94. View
    action Represents an interaction
    Is not state
    Includes an action type
    Includes relevant data
    Is a value

    View full-size slide

  95. View
    Store
    Dispatcher
    state
    action
    state

    View full-size slide

  96. View
    Dispatcher
    state
    action

    View full-size slide

  97. View
    Dispatcher
    state
    action
    Receives actions

    View full-size slide

  98. View
    Dispatcher
    state
    action
    Receives actions
    Forwards to subscribers

    View full-size slide

  99. View
    Dispatcher
    state
    action
    Receives actions
    Forwards to subscribers
    Has multiple subscribers

    View full-size slide

  100. View
    Dispatcher
    state
    action
    Receives actions
    Forwards to subscribers
    Has multiple subscribers
    Is a class

    View full-size slide

  101. View
    Store
    Dispatcher
    state
    action
    state

    View full-size slide

  102. View
    Store
    Dispatcher
    state
    action
    action
    state
    Store
    state

    View full-size slide

  103. Store
    action
    state

    View full-size slide

  104. Store
    action
    +
    current state
    state

    View full-size slide

  105. Store
    current state action
    +
    next state
    state

    View full-size slide

  106. Store
    state
    View
    Store
    Dispatcher
    state
    action
    action
    state
    action

    View full-size slide

  107. Store
    state
    View
    Store
    Dispatcher
    state
    action
    action
    state
    action

    View full-size slide

  108. A SIMPLE EXAMPLE

    View full-size slide

  109. class CounterViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTapped(sender: AnyObject) {
    }
    }

    View full-size slide

  110. class CounterViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTapped(sender: AnyObject) {
    if let text = label.text,
    count = Int(text) {
    label.text = "\(count + 1)"
    }
    }
    }

    View full-size slide

  111. class CounterViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTapped(sender: AnyObject) {
    }
    }

    View full-size slide

  112. class CounterViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    private var count: Int
    @IBAction func buttonTapped(sender: AnyObject) {
    count += 1
    }
    }

    View full-size slide

  113. class CounterViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    private var count: Int
    @IBAction func buttonTapped(sender: AnyObject) {
    count += 1
    label.text = "\(count)"
    }
    }

    View full-size slide

  114. class CounterViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTapped(sender: AnyObject) {
    }
    }

    View full-size slide

  115. class CounterViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTapped(sender: AnyObject) {
    dispatcher.dispatch(.Increment)
    }
    }

    View full-size slide

  116. class CounterViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTapped(sender: AnyObject) {
    dispatcher.dispatch(.Increment)
    }
    }
    extension CounterViewController: Subscriber {
    }

    View full-size slide

  117. class CounterViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTapped(sender: AnyObject) {
    dispatcher.dispatch(.Increment)
    }
    }
    extension CounterViewController: Subscriber {
    func receive(state: State) {
    label.text = "\(state.count)"
    }
    }

    View full-size slide

  118. WHAT DID THIS ACCOMPLISH

    View full-size slide

  119. Isolates state to a single component
    WHAT DID THIS ACCOMPLISH

    View full-size slide

  120. Isolates state to a single component
    Uses values to communicate state
    WHAT DID THIS ACCOMPLISH

    View full-size slide

  121. Isolates state to a single component
    Uses values to communicate state
    Eliminates state from the UI layer
    WHAT DID THIS ACCOMPLISH

    View full-size slide

  122. Handle view reuse
    Support complex, dynamic UIs
    Automatically animate changes
    COLLECTION VIEWS

    View full-size slide

  123. Buggy
    Crashy
    Frustrating
    COLLECTION VIEWS

    View full-size slide

  124. Buggy
    Crashy
    Frustrating
    Why?
    COLLECTION VIEWS

    View full-size slide

  125. COLLECTION VIEWS

    View full-size slide

  126. Contrast between implementations
    COLLECTION VIEWS

    View full-size slide

  127. Contrast between implementations
    Trickiness of mutability
    COLLECTION VIEWS

    View full-size slide

  128. Contrast between implementations
    Trickiness of mutability
    Statelessness and testability
    COLLECTION VIEWS

    View full-size slide

  129. Contrast between implementations
    Trickiness of mutability
    Statelessness and testability
    Statelessness is the future
    COLLECTION VIEWS

    View full-size slide

  130. Collection View
    Data Source

    View full-size slide

  131. Collection View
    Data Source
    how many sections?

    View full-size slide

  132. 3
    Collection View
    sectionCount
    Data Source
    how many sections?

    View full-size slide

  133. 3
    1
    Collection View
    sectionCount
    itemCounts
    Data Source
    how many sections?
    items in section 0?

    View full-size slide

  134. 3
    1
    item
    Collection View
    sectionCount
    itemCounts
    visibleItems
    Data Source
    how many sections?
    items in section 0?
    give me item 0, 0

    View full-size slide

  135. Collection View
    Data Source
    View Controller

    View full-size slide

  136. Collection View
    Model View Controller

    View full-size slide

  137. Collection View
    Model View Controller
    item
    item
    item
    item

    View full-size slide

  138. Collection View
    Model View Controller
    item
    delete item
    item
    item

    View full-size slide

  139. Collection View
    Model View Controller
    item
    delete item
    delete item
    item

    View full-size slide

  140. Collection View
    Model View Controller
    item
    delete item
    delete item
    1
    items in section 0?
    item

    View full-size slide

  141. Collection View
    Model View Controller
    item
    delete item
    delete item
    insert item
    1
    items in section 0?
    item
    item

    View full-size slide

  142. Collection View
    Model View Controller
    item
    delete item
    delete item
    insert item
    insert item
    2
    items in section 0?
    1
    items in section 0?
    item
    item
    item

    View full-size slide

  143. Business logic in the model
    PITFALLS

    View full-size slide

  144. Business logic in the model
    No knowledge of the past
    PITFALLS

    View full-size slide

  145. Business logic in the model
    No knowledge of the past
    Mutable state breeds complexity
    PITFALLS

    View full-size slide

  146. Collection View
    Model View Controller

    View full-size slide

  147. Collection View
    Model View Controller
    insert item

    View full-size slide

  148. Collection View
    Model View Controller
    insert item
    item
    item

    View full-size slide

  149. Collection View
    Model View Controller
    insert item
    insert item
    item
    item
    item

    View full-size slide

  150. Collection View
    Model View Controller
    insert item
    insert item
    2
    items in section 0?
    item
    item
    item

    View full-size slide

  151. Collection View
    Model View Controller
    insert item
    insert item
    2
    items in section 0?
    item
    item
    item

    View full-size slide

  152. Collection View
    Model View Controller
    insert item
    insert item
    2
    items in section 0?
    item item
    insert item
    item item

    View full-size slide

  153. Collection View
    Model View Controller
    insert item
    insert item
    2
    items in section 0?
    item item
    delete item
    delete item
    1
    items in section 0?
    insert item

    View full-size slide

  154. Collection View
    Model View Controller
    insert item
    insert item
    2
    items in section 0?
    item item

    delete item
    delete item
    1
    items in section 0?
    insert item

    View full-size slide

  155. reloadItemsAtIndexPaths
    reloadSections
    reloadData
    EXTREME MEASURES

    View full-size slide

  156. current state

    View full-size slide

  157. current state
    next state

    View full-size slide

  158. current state
    next state
    current state
    next state
    changeset

    View full-size slide

  159. current state
    next state
    current state
    next state
    changeset
    Collection View

    View full-size slide

  160. current state
    next state
    current state
    next state
    changeset
    next state
    current state
    next state
    changeset
    nex
    curre
    nex
    cha
    Collection View Collection View Collecti

    View full-size slide

  161. current state
    next state
    changeset

    View full-size slide

  162. current state next state changeset
    Grapes
    Nutella
    Gummi bears
    Pizza

    View full-size slide

  163. current state next state changeset
    Grapes
    Nutella
    Gummi bears
    Pizza
    Grapes
    Nutella
    Doritos
    Gummi bears
    Pizza

    View full-size slide

  164. current state next state changeset
    Grapes
    Nutella
    Gummi bears
    Pizza
    Grapes
    Nutella
    Doritos
    Gummi bears
    Pizza
    Insert at index 2

    View full-size slide

  165. current state next state changeset
    Grapes
    Nutella
    Gummi bears
    Pizza
    Grapes
    Nutella
    Doritos
    Gummi bears
    Pizza
    Insert at index 2

    Collection view states are lists of items
    Only needs to be written once
    Huge win for testability

    View full-size slide

  166. struct Item {
    let title: String
    }

    View full-size slide

  167. struct Item {
    let title: String
    }
    extension Item: Equatable {}
    func ==(lhs: Item, rhs: Item) -> Bool {
    return lhs.title == rhs.title
    }

    View full-size slide

  168. struct Item {
    let title: String
    }
    extension Item: Equatable {}
    func ==(lhs: Item, rhs: Item) -> Bool {
    return lhs.title == rhs.title
    }
    let item1 = Item(title: "Doritos")
    let item2 = Item(title: "Cheetos")
    item1 == item2 // false

    View full-size slide

  169. protocol Identifiable {
    var id: String { get }
    }

    View full-size slide

  170. protocol Identifiable {
    var id: String { get }
    }
    struct Item: Equatable, Identifiable {
    let id: String
    let title: String
    }

    View full-size slide

  171. protocol Identifiable {
    var id: String { get }
    }
    struct Item: Equatable, Identifiable {
    let id: String
    let title: String
    }
    let item1 = Item(id: "1", title: "Doritos")
    let item2 = Item(id: "1", title: "Cheetos")
    item1.id == item2.id // true
    item1 == item2 // false

    View full-size slide

  172. let state1 = [Item(id: "A", title: "Gummi bears")]
    let state2 = [Item(id: "B", title: "Cheetos")]
    state1.difference(state2) // [.Delete(0), .Insert(0)]

    View full-size slide

  173. let state1 = [Item(id: "A", title: "Gummi bears")]
    let state2 = [Item(id: "B", title: "Cheetos")]
    state1.difference(state2) // [.Delete(0), .Insert(0)]
    extension CollectionType where
    Generator.Element: Equatable,
    Generator.Element: Identifiable {
    }

    View full-size slide

  174. let state1 = [Item(id: "A", title: "Gummi bears")]
    let state2 = [Item(id: "B", title: "Cheetos")]
    state1.difference(state2) // [.Delete(0), .Insert(0)]
    extension CollectionType where
    Generator.Element: Equatable,
    Generator.Element: Identifiable {
    func difference(other: Self) -> [Change] {
    var changes = [Change]()
    // TODO
    return changes
    }
    }

    View full-size slide

  175. let state1 = [Item(id: "A", title: "Gummi bears")]
    let state2 = [Item(id: "B", title: "Cheetos")]
    state1.difference(state2) // [.Delete(0), .Insert(0)]
    extension CollectionType where
    Generator.Element: Equatable,
    Generator.Element: Identifiable {
    func difference(other: Self) -> [Change] {
    var changes = [Change]()
    // TODO
    return changes
    }
    }
    enum Change {
    case Delete(Int)
    case Insert(Int)
    case Update(Int)
    }

    View full-size slide

  176. for (index, item) in other.enumerate() {
    if !contains({ $0.id == item.id }) {
    changes.append(.Insert(index))
    }
    }
    for (index, item) in enumerate() {
    if !other.contains({ $0.id == item.id }) {
    changes.append(.Delete(index))
    }
    }
    for (index, item) in enumerate() {
    if other.contains({ $0.id == item.id && $0 != item }) {
    changes.append(.Update(index))
    }
    }

    View full-size slide

  177. for (index, item) in other.enumerate() {
    if !contains({ $0.id == item.id }) {
    changes.append(.Insert(index))
    }
    }
    for (index, item) in enumerate() {
    if !other.contains({ $0.id == item.id }) {
    changes.append(.Delete(index))
    }
    }
    for (index, item) in enumerate() {
    if other.contains({ $0.id == item.id && $0 != item }) {
    changes.append(.Update(index))
    }
    }

    View full-size slide

  178. for (index, item) in other.enumerate() {
    if !contains({ $0.id == item.id }) {
    changes.append(.Insert(index))
    }
    }
    for (index, item) in enumerate() {
    if !other.contains({ $0.id == item.id }) {
    changes.append(.Delete(index))
    }
    }
    for (index, item) in enumerate() {
    if other.contains({ $0.id == item.id && $0 != item }) {
    changes.append(.Update(index))
    }
    }

    View full-size slide

  179. for (index, item) in other.enumerate() {
    if !contains({ $0.id == item.id }) {
    changes.append(.Insert(index))
    }
    }
    for (index, item) in enumerate() {
    if !other.contains({ $0.id == item.id }) {
    changes.append(.Delete(index))
    }
    }
    for (index, item) in enumerate() {
    if other.contains({ $0.id == item.id && $0 != item }) {
    changes.append(.Update(index))
    }
    }

    View full-size slide

  180. Goes against the grain of UIKit
    Animations
    Performance
    Debugging
    CHALLENGES

    View full-size slide

  181. Encapsulate complete app state
    Test fuzzing
    Time travel
    CURRENT WORK

    View full-size slide

  182. http://livefront.com/speaks/stateless/
    Adam May @yammada
    Sam Kirchmeier @skirchmeier
    THANKS!

    View full-size slide