| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- 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<Value>: 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<Value> {
- 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
|