1
0

Published.swift 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import Foundation
  2. /// ``ObservableObject`` values nested within an ``ObservableObject`` object
  3. /// will only have their changes published by the parent ``ObservableObject``
  4. /// if marked with this marker protocol. This avoids uncertainty around which
  5. /// properties will or will not have their changes published by the parent.
  6. /// For clarity reasons, you shouldn't conform your own types to this protocol.
  7. /// Instead, apply the ``Published`` property wrapper when needed.
  8. ///
  9. /// ```swift
  10. /// // The following example highlights why the marker protocol exists.
  11. ///
  12. /// class MyNestedState: ObservableObject {
  13. /// @Published var count = 0
  14. /// }
  15. ///
  16. /// class MyState: ObservableObject {
  17. /// // Without the marker protocol mechanism in place, `nested` would get
  18. /// // published as well as `index`. However, that would not be possible to
  19. /// // know without looking at the definition to check if `MyNestedState`
  20. /// // is `ObservableObject`. Because of the marker protocol, it is required
  21. /// // that both properties are annotated with `@Published` (which conforms
  22. /// // to the marker protocol).
  23. /// var nested = MyNestedState()
  24. /// @Published var index = 0
  25. /// }
  26. /// ```
  27. ///
  28. public protocol PublishedMarkerProtocol {}
  29. /// A wrapper which publishes a change whenever the wrapped value is set. If
  30. /// the wrapped value is ``ObservableObject``, its `didChange` publisher will
  31. /// also be forwarded to the wrapper's publisher.
  32. ///
  33. /// A compile time warning is emitted if the wrapper is applied to a class
  34. /// which isn't ``ObservableObject`` because this is considered undesired
  35. /// behaviour. Only replacing the value with a new instance of the class would
  36. /// cause a change to be published; changing the class' properties would not.
  37. /// The warning will show up as a deprecation, but it isn't (as you could guess
  38. /// from the accompanying message).
  39. @propertyWrapper
  40. public final class Published<Value>: ObservableObject, PublishedMarkerProtocol {
  41. /// A handle that can be used to cancel the link to the previous upstream publisher.
  42. private var upstreamLinkCancellable: Cancellable?
  43. /// A binding to the inner value.
  44. public var projectedValue: Binding<Value> {
  45. Binding(
  46. get: {
  47. self.wrappedValue
  48. },
  49. set: { newValue in
  50. self.wrappedValue = newValue
  51. }
  52. )
  53. }
  54. /// The underlying wrapped value.
  55. public var wrappedValue: Value {
  56. didSet {
  57. valueDidChange()
  58. }
  59. }
  60. /// A publisher that publishes any observable changes made to
  61. /// ``Published/wrappedValue``.
  62. public let didChange = Publisher().tag(with: "Published")
  63. /// Creates a publishing wrapper around a value type or
  64. /// ``ObservableObject`` class.
  65. public init(wrappedValue: Value) {
  66. self.wrappedValue = wrappedValue
  67. valueDidChange(publish: false)
  68. }
  69. /// Creates a publishing wrapper around a value type or ``ObservableObject``
  70. /// class.
  71. public init(wrappedValue: Value) where Value: AnyObject, Value: ObservableObject {
  72. // This initializer exists to redirect valid classes away from the initializer which
  73. // contains a compile time warning (through deprecation).
  74. self.wrappedValue = wrappedValue
  75. valueDidChange(publish: false)
  76. }
  77. /// Creates a wrapper around a non-ObservableObject class. Setting
  78. /// ``Published/wrappedValue`` to a new instance of the class is the only
  79. /// change that will get published. This is hardly ever intentional, so
  80. /// this initializer variant contains a deprecation warning to warn
  81. /// developers (but does nothing functionally different).
  82. @available(
  83. *,
  84. deprecated,
  85. message: "A class must conform to ObservableObject to be Published"
  86. )
  87. public init(wrappedValue: Value) where Value: AnyObject {
  88. self.wrappedValue = wrappedValue
  89. valueDidChange(publish: false)
  90. }
  91. /// Handles changing a value. If `publish` is `false` the change won't be
  92. /// published, but if the wrapped value is ``ObservableObject`` the new
  93. /// upstream publisher will still get relinked.
  94. public func valueDidChange(publish: Bool = true) {
  95. if publish {
  96. didChange.send()
  97. }
  98. if let upstream = wrappedValue as? ObservableObject {
  99. upstreamLinkCancellable?.cancel()
  100. upstreamLinkCancellable = didChange.link(toUpstream: upstream.didChange)
  101. }
  102. }
  103. }
  104. extension Published: Codable where Value: Codable {
  105. public convenience init(from decoder: Decoder) throws {
  106. self.init(wrappedValue: try Value(from: decoder))
  107. }
  108. public func encode(to encoder: Encoder) throws {
  109. try wrappedValue.encode(to: encoder)
  110. }
  111. }
  112. @available(*, deprecated, message: "Replace Observed with Published")
  113. public typealias Observed = Published