StateImpl.swift 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. struct StateImpl<Storage: StateStorageProtocol> {
  2. /// The inner storage of `StateImpl`.
  3. ///
  4. /// The inner `Storage` is what stays constant between view updates.
  5. /// The wrapping box is used so that we can assign the storage to future
  6. /// state instances from the non-mutating ``update(with:previousValue:)``
  7. /// method. It's vital that the inner storage remains the same so that
  8. /// bindings can be stored across view updates.
  9. var box: Box<Storage>
  10. var storage: Storage {
  11. get { box.value }
  12. nonmutating set { box.value = newValue }
  13. }
  14. init(initialStorage: Storage) {
  15. self.box = Box(initialStorage)
  16. // Before casting the value we check the type, because casting an optional
  17. // to protocol Optional doesn't conform to can still succeed when the value
  18. // is `.some` and the wrapped type conforms to the protocol.
  19. if Storage.Value.self is ObservableObject.Type,
  20. let value = initialStorage.value as? ObservableObject
  21. {
  22. storage.downstreamObservation = storage.didChange.link(toUpstream: value.didChange)
  23. } else if
  24. let value = initialStorage.value as? OptionalObservableObject,
  25. let innerDidChange = value.didChange
  26. {
  27. // If we have an `Optional<some ObservableObject>.some`, then observe its
  28. // inner value's publisher.
  29. storage.downstreamObservation = storage.didChange.link(toUpstream: innerDidChange)
  30. }
  31. }
  32. var wrappedValue: Storage.Value {
  33. get { storage.value }
  34. nonmutating set {
  35. storage.value = newValue
  36. storage.postSet()
  37. }
  38. }
  39. var projectedValue: Binding<Storage.Value> {
  40. // Specifically link the binding to the inner storage instead of the
  41. // outer box which changes with each view update.
  42. let storage = storage
  43. return Binding(
  44. get: { storage.value },
  45. set: { newValue in
  46. storage.value = newValue
  47. storage.postSet()
  48. }
  49. )
  50. }
  51. func update(with environment: EnvironmentValues, previousValue: Self?) {
  52. if let previousValue {
  53. storage = previousValue.storage
  54. }
  55. }
  56. }
  57. protocol StateStorageProtocol: AnyObject {
  58. associatedtype Value
  59. var value: Value { get set }
  60. var didChange: Publisher { get }
  61. var downstreamObservation: Cancellable? { get set }
  62. }
  63. extension StateStorageProtocol {
  64. /// Call this to publish an observation to all observers after
  65. /// setting a new value. This isn't in a `didSet` property accessor
  66. /// because we want more granular control over when it does and
  67. /// doesn't trigger.
  68. ///
  69. /// Additionally updates the downstream observation if the
  70. /// wrapped value is an `Optional<some ObservableObject>` and the
  71. /// current case has toggled.
  72. func postSet() {
  73. // If the wrapped value is an `Optional<some ObservableObject>`
  74. // then we need to observe/unobserve whenever the optional
  75. // toggles between `.some` and `.none`.
  76. if let value = value as? OptionalObservableObject {
  77. if let innerDidChange = value.didChange, downstreamObservation == nil {
  78. downstreamObservation = didChange.link(toUpstream: innerDidChange)
  79. } else if value.didChange == nil, let observation = downstreamObservation {
  80. observation.cancel()
  81. downstreamObservation = nil
  82. }
  83. }
  84. didChange.send()
  85. }
  86. }