Advertisement
Guest User

Untitled

a guest
Jun 18th, 2019
107
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.46 KB | None | 0 0
  1. // This is a re-implementation of the @Binding and @State property wrappers from SwiftUI
  2. // The only purpose of this code is to implement those wrappers myself just to understand how they work internally and why they are needed
  3. // Re-implementing them myself has helped me understand the whole thing better
  4.  
  5. //: # A Binding is just something that encapsulates getter+setter to a property
  6.  
  7. @propertyDelegate
  8. struct XBinding<Value> {
  9. var value: Value {
  10. get { return getValue() }
  11. nonmutating set { setValue(newValue) }
  12. }
  13.  
  14. private let getValue: () -> Value
  15. private let setValue: (Value) -> Void
  16.  
  17. init(getValue: @escaping () -> Value, setValue: @escaping (Value) -> Void) {
  18. self.getValue = getValue
  19. self.setValue = setValue
  20. }
  21. }
  22.  
  23. //: -----------------------------------------------------------------
  24. //: ## Simple Int example
  25.  
  26. // We need a storage to reference first
  27. private var x1Storage: Int = 42
  28.  
  29. // (Note: Creating a struct because top-level property wrappers don't work well at global scope in a playground
  30. // – globals being lazy and all)
  31. struct Example1 {
  32. @XBinding(getValue: { x1Storage }, setValue: { x1Storage = $0 })
  33. var x1: Int
  34. /* The propertyWrapper translates this to
  35. var $x1 = XBinding<Int>(getValue: { x1Storage }, setValue: { x1Storage = $0 })
  36. var x1: Int {
  37. get { return _x1.value } // which in turn ends up using the getValue closure
  38. set { _x1.value = newValue } // which in turn ends up using the setValue closure
  39. }
  40. */
  41.  
  42. func run() {
  43. print("Before:", "x1Storage =", x1Storage, "x1 =", x1) // Before: x1Storage = 42 x1 = 42
  44. x1 = 37
  45. print("After:", "x1Storage =", x1Storage, "x1 =", x1) // After: x1Storage = 37 x1 = 37
  46. }
  47. }
  48. Example1().run()
  49.  
  50. // This works, but as you can see, we had to create the storage ourself in order to then create a @Binding
  51. // Which is not ideal, since we have to create some property in one place (x1Storage),
  52. // then create a binding to that property separately to reference and manipulate it via the Binding
  53. // We'll see later how we can solve that.
  54.  
  55.  
  56.  
  57.  
  58. //: -----------------------------------------------------------------
  59. //: ## Manipulating compound types
  60.  
  61. // In the meantime, let's play a little with Bindings. Let's create a Binding on a more complex type:
  62.  
  63. struct Address {
  64. var street: String
  65. }
  66.  
  67. struct Person {
  68. var name: String
  69. var address: Address
  70. }
  71.  
  72. var personStorage = Person(name: "Olivier", address: Address(street: "Playground Street"))
  73.  
  74. struct Example2 {
  75. @XBinding(getValue: { personStorage }, setValue: { personStorage = $0 })
  76. var person: Person
  77. /* Translated to: */
  78. // var $person = XBinding<Person>(getValue: { personStorage }, setValue: { personStorage = $0 })
  79. // var person: Person { get { $person.value } set { $person.value = newValue } }
  80.  
  81. func run() {
  82. print(person.name) // "Olivier"
  83. print($person.value.name) // Basically the same as above, just more verbose
  84. }
  85. }
  86. let example2 = Example2()
  87. example2.run()
  88.  
  89. // Ok, that's not so useful so far, be what if we could now `map` to inner properties of the Person
  90. // i.e. what if I now want to transform the `Binding<Person>` to a `Binding<String>` now pointing to the `name` property?
  91.  
  92.  
  93.  
  94.  
  95. //: -----------------------------------------------------------------
  96. //: # Transform Bindings
  97.  
  98. // Usually in monad-land, we could declare a `map` method on XBinding for that
  99. // Except that here we need to be able to both get the name from the person... and be able to set it too
  100. // So instead of using a `transform` like classic `map`, we're gonna use a WritableKeyPath to be able to go both directions
  101.  
  102. extension XBinding {
  103. func map<NewValue>(_ keyPath: WritableKeyPath<Value, NewValue>) -> XBinding<NewValue> {
  104. return XBinding<NewValue>(
  105. getValue: { self.value[keyPath: keyPath] },
  106. setValue: { self.value[keyPath: keyPath] = $0 }
  107. )
  108. }
  109. }
  110.  
  111. let nameBinding = example2.$person.map(\.name) // We now have a binding to the name property inside the Person
  112. nameBinding.value = "NewName"
  113. print(personStorage.name) // "NewName"
  114.  
  115. // But why stop there? Instead of having to call `$person.map(\.name)`, wouldn't it be better to call $person.name directly?
  116. // Let's do that using @dynamicMemberLookup. (We'll add that via protocol conformance so we can reuse this feature easily on other types later)
  117.  
  118.  
  119. //: -----------------------------------------------------------------
  120. //: # dynamicMemberLoopup
  121. //: Add dynamic member lookup capability (via protocol conformance) to forward any access to a property to the inner value
  122.  
  123. @dynamicMemberLookup protocol XBindingConvertible {
  124. associatedtype Value
  125.  
  126. var binding: XBinding<Self.Value> { get }
  127.  
  128. subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Self.Value, Subject>) -> XBinding<Subject> { get }
  129. }
  130.  
  131. extension XBindingConvertible {
  132. public subscript<Subject>(dynamicMember keyPath: WritableKeyPath<Self.Value, Subject>) -> XBinding<Subject> {
  133. return XBinding(
  134. getValue: { self.binding.value[keyPath: keyPath] },
  135. setValue: { self.binding.value[keyPath: keyPath] = $0 }
  136. )
  137. }
  138. }
  139.  
  140. // XBinding is one of those types on which we want that dynamicMemberLookup feature:
  141. extension XBinding: XBindingConvertible {
  142. var binding: XBinding<Value> {
  143. return self
  144. }
  145. }
  146.  
  147. // And now e2.$person.name transforms the `e2.$person: XBinding<Person>` into a `XBinding<String>`
  148. // which is now bound to the `.name` property of the Person
  149. print(type(of: example2.$person.name)) // XBinding<String>
  150. let streetBinding: XBinding<String> = example2.$person.address.street
  151. streetBinding.value = "Xcode Avenue"
  152. print(example2.person) // Person(name: "NewName", address: __lldb_expr_17.Address(street: "Xcode Avenue"))
  153.  
  154.  
  155.  
  156.  
  157.  
  158.  
  159.  
  160. //: -----------------------------------------------------------------
  161. //: # We don't want to declare storage ourselves
  162.  
  163. //: Ok this is all good and well, but remember our issue from the beginning? We still need to declare the storage for the value ourselves
  164. //: Currently we had to declare personStorage and had to explicitly say how to get/set that storage when defining our XBinding
  165. //: That's no fun, let's wrap that one level further
  166.  
  167. // XState will wrap both the storage for the value, and a Binding to it
  168.  
  169. @propertyDelegate class XState<Value>: XBindingConvertible {
  170. var value: Value
  171. var binding: XBinding<Value> { delegateValue }
  172.  
  173. init(initialValue value: Value) {
  174. self.value = value
  175. }
  176.  
  177. var delegateValue: XBinding<Value> {
  178. XBinding(getValue: { self.value }, setValue: { self.value = $0 })
  179. }
  180. }
  181.  
  182. // And now we don't need to declare both the personStorage and the @Binding person property, we can use @State person and have it all
  183. struct Example3 {
  184. @XState var person = Person(name: "Bob", address: Address(street: "Builder Street"))
  185. // Note that since `delegateValue` (renamed wrapperValue in the SE proposal) expses an XBinding, $person will be a XBinding, not an XState here
  186. // So this is translated to:
  187. // var $person: XBinding(getValue: { self.storage }, setValue: { self.storage = $0 })
  188. // var person: Person { get { $person.value } set { $person.value = newValue } }
  189.  
  190. func run() {
  191. print(person.name)
  192.  
  193. let streetBinding: XBinding<String> = $person.address.street
  194. person = Person(name: "Crusty", address: Address(street: "WWDC Stage"))
  195. streetBinding.value = "Memory Lane"
  196. print(person) // Person(name: "Crusty", address: __lldb_expr_17.Address(street: "Memory Lane"))
  197. }
  198. }
  199. Example3().run()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement