diff --git a/.spi.yml b/.spi.yml new file mode 100644 index 0000000..f780de7 --- /dev/null +++ b/.spi.yml @@ -0,0 +1,5 @@ +version: 1 +builder: + configs: + - documentation_targets: + - Publishable diff --git a/Sources/Publishable/Documentation.docc/Publishable.md b/Sources/Publishable/Documentation.docc/Publishable.md new file mode 100644 index 0000000..ad46d0c --- /dev/null +++ b/Sources/Publishable/Documentation.docc/Publishable.md @@ -0,0 +1,14 @@ +# ``Publishable-module`` + +Observe changes to `Observable` types synchronously with `Combine`. + +## Topics + +### Making Types Publishable + +- ``Publishable()`` +- ``Publishable-protocol`` + +### Getting Property Publishers + +- ``AnyPropertyPublisher`` diff --git a/Sources/Publishable/PropertyPublisher/AnyPropertyPublisher.swift b/Sources/Publishable/PropertyPublisher/AnyPropertyPublisher.swift index df12fbf..ba73ed8 100644 --- a/Sources/Publishable/PropertyPublisher/AnyPropertyPublisher.swift +++ b/Sources/Publishable/PropertyPublisher/AnyPropertyPublisher.swift @@ -8,15 +8,24 @@ import Combine +/// An object that exposes `Combine` publishers for ``willChange`` and ``didChange`` events. +/// +/// Subclasses of this class are generated by the ``Publishable()`` macro, +/// and provide publishers for all mutable instance properties of the type the macro is applied to. +/// open class AnyPropertyPublisher { private let _willChange = PassthroughSubject() private let _didChange = PassthroughSubject() + /// Emits the `Object` **before** any of its stored properties are assigned a new value. + /// public var willChange: AnyPublisher { _willChange.eraseToAnyPublisher() } + /// Emits the `Object` **after** any of its stored properties are assigned a new value. + /// public var didChange: AnyPublisher { _didChange.eraseToAnyPublisher() } diff --git a/Sources/Publishable/PropertyPublisher/Publishable.swift b/Sources/Publishable/PropertyPublisher/Publishable.swift index 63b631a..cc82b65 100644 --- a/Sources/Publishable/PropertyPublisher/Publishable.swift +++ b/Sources/Publishable/PropertyPublisher/Publishable.swift @@ -6,6 +6,22 @@ // Copyright © 2025 Kamil Strzelecki. All rights reserved. // +import Observation + +/// A macro that adds ``Publishable-protocol`` conformance to `Observable` types. +/// +/// - Note: This macro works only with `final` classes to which the `Observable` or `SwiftData.Model` macro has been applied directly. +/// +/// The `Publishable` macro adds a new ``Publishable/publisher`` property to your type, +/// which exposes `Combine` publishers for all mutable instance properties - both stored and computed. +/// +/// If a property’s type conforms to `Equatable`, its publisher automatically removes duplicate values. +/// Just like the `Published` property wrapper, subscribing to any of the exposed publishers immediately emits the current value. +/// +/// - Important: Swift Macros do not have access to full type information of expressions used in the code they’re applied to. +/// Since working with `Combine` requires knowledge of concrete types, this macro attempts to infer the types of properties when they are not explicitly specified. +/// However, this inference may fail in non-trivial cases. If the generated code fails to compile, explicitly specifying the type of the affected property should resolve the issue. +/// @attached( member, names: named(_publisher), @@ -22,9 +38,22 @@ public macro Publishable() = #externalMacro( type: "PublishableMacro" ) -public protocol Publishable: AnyObject { +/// A type that can be observed using both the `Observation` and `Combine` frameworks. +/// +/// You don't need to declare conformance to this protocol yourself. +/// It is generated automatically when you apply the ``Publishable()`` macro to your type. +/// +public protocol Publishable: AnyObject, Observable { + /// A subclass of ``AnyPropertyPublisher`` generated by the ``Publishable()`` macro, + /// containing publishers for all mutable instance properties of the type. + /// associatedtype PropertyPublisher: AnyPropertyPublisher + /// An instance that exposes `Combine` publishers for all mutable instance properties of the type. + /// + /// - Important: Don't store this instance in an external property. Accessing it after the original object has been deallocated + /// may result in a crash. Always access it directly through the object that exposes it. + /// var publisher: PropertyPublisher { get } } diff --git a/Sources/Publishable/Registrars/PublishableObservationRegistrar.swift b/Sources/Publishable/Registrars/PublishableObservationRegistrar.swift index 12fd957..245bf25 100644 --- a/Sources/Publishable/Registrars/PublishableObservationRegistrar.swift +++ b/Sources/Publishable/Registrars/PublishableObservationRegistrar.swift @@ -8,6 +8,7 @@ import Observation +@_documentation(visibility: private) public protocol PublishableObservationRegistrar { associatedtype Object: Publishable, Observable diff --git a/Sources/Publishable/Registrars/SwiftObservationRegistrar.swift b/Sources/Publishable/Registrars/SwiftObservationRegistrar.swift index 8cafde0..4afa00a 100644 --- a/Sources/Publishable/Registrars/SwiftObservationRegistrar.swift +++ b/Sources/Publishable/Registrars/SwiftObservationRegistrar.swift @@ -8,4 +8,5 @@ import Observation +@_documentation(visibility: private) // swiftformat:disable:next all public typealias SwiftObservationRegistrar = ObservationRegistrar