import Foundation /// ``ObservableObject`` values nested within an ``ObservableObject`` object /// will only have their changes published by the parent ``ObservableObject`` /// if marked with this marker protocol. This avoids uncertainty around which /// properties will or will not have their changes published by the parent. /// For clarity reasons, you shouldn't conform your own types to this protocol. /// Instead, apply the ``Published`` property wrapper when needed. /// /// ```swift /// // The following example highlights why the marker protocol exists. /// /// class MyNestedState: ObservableObject { /// @Published var count = 0 /// } /// /// class MyState: ObservableObject { /// // Without the marker protocol mechanism in place, `nested` would get /// // published as well as `index`. However, that would not be possible to /// // know without looking at the definition to check if `MyNestedState` /// // is `ObservableObject`. Because of the marker protocol, it is required /// // that both properties are annotated with `@Published` (which conforms /// // to the marker protocol). /// var nested = MyNestedState() /// @Published var index = 0 /// } /// ``` /// public protocol PublishedMarkerProtocol {} /// A wrapper which publishes a change whenever the wrapped value is set. If /// the wrapped value is ``ObservableObject``, its `didChange` publisher will /// also be forwarded to the wrapper's publisher. /// /// A compile time warning is emitted if the wrapper is applied to a class /// which isn't ``ObservableObject`` because this is considered undesired /// behaviour. Only replacing the value with a new instance of the class would /// cause a change to be published; changing the class' properties would not. /// The warning will show up as a deprecation, but it isn't (as you could guess /// from the accompanying message). @propertyWrapper public final class Published: ObservableObject, PublishedMarkerProtocol { /// A handle that can be used to cancel the link to the previous upstream publisher. private var upstreamLinkCancellable: Cancellable? /// A binding to the inner value. public var projectedValue: Binding { Binding( get: { self.wrappedValue }, set: { newValue in self.wrappedValue = newValue } ) } /// The underlying wrapped value. public var wrappedValue: Value { didSet { valueDidChange() } } /// A publisher that publishes any observable changes made to /// ``Published/wrappedValue``. public let didChange = Publisher().tag(with: "Published") /// Creates a publishing wrapper around a value type or /// ``ObservableObject`` class. public init(wrappedValue: Value) { self.wrappedValue = wrappedValue valueDidChange(publish: false) } /// Creates a publishing wrapper around a value type or ``ObservableObject`` /// class. public init(wrappedValue: Value) where Value: AnyObject, Value: ObservableObject { // This initializer exists to redirect valid classes away from the initializer which // contains a compile time warning (through deprecation). self.wrappedValue = wrappedValue valueDidChange(publish: false) } /// Creates a wrapper around a non-ObservableObject class. Setting /// ``Published/wrappedValue`` to a new instance of the class is the only /// change that will get published. This is hardly ever intentional, so /// this initializer variant contains a deprecation warning to warn /// developers (but does nothing functionally different). @available( *, deprecated, message: "A class must conform to ObservableObject to be Published" ) public init(wrappedValue: Value) where Value: AnyObject { self.wrappedValue = wrappedValue valueDidChange(publish: false) } /// Handles changing a value. If `publish` is `false` the change won't be /// published, but if the wrapped value is ``ObservableObject`` the new /// upstream publisher will still get relinked. public func valueDidChange(publish: Bool = true) { if publish { didChange.send() } if let upstream = wrappedValue as? ObservableObject { upstreamLinkCancellable?.cancel() upstreamLinkCancellable = didChange.link(toUpstream: upstream.didChange) } } } extension Published: Codable where Value: Codable { public convenience init(from decoder: Decoder) throws { self.init(wrappedValue: try Value(from: decoder)) } public func encode(to encoder: Encoder) throws { try wrappedValue.encode(to: encoder) } } @available(*, deprecated, message: "Replace Observed with Published") public typealias Observed = Published