From bc05e17c920badf211b173d4eb9253fad9e522d4 Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sat, 26 Apr 2025 11:38:44 -0700 Subject: [PATCH] Add documentation comments on most public API --- .../AsyncMonitor/AnyAsyncCancellable.swift | 14 ++------- Sources/AsyncMonitor/AsyncCancellable.swift | 20 ++++++++++++- Sources/AsyncMonitor/AsyncMonitor.swift | 29 ++++++++++++------- .../AsyncSequence+Extensions.swift | 16 ++++++++++ Sources/AsyncMonitor/NSObject+AsyncKVO.swift | 6 ++++ 5 files changed, 63 insertions(+), 22 deletions(-) diff --git a/Sources/AsyncMonitor/AnyAsyncCancellable.swift b/Sources/AsyncMonitor/AnyAsyncCancellable.swift index 1d6e1c0..ca169d5 100644 --- a/Sources/AsyncMonitor/AnyAsyncCancellable.swift +++ b/Sources/AsyncMonitor/AnyAsyncCancellable.swift @@ -1,7 +1,9 @@ +/// Type-erasing wrapper for ``AsyncCancellable`` that ties its instance lifetime to cancellation. In other words, when you release +/// an instance of ``AnyAsyncCancellable`` and it's deallocated then it automatically cancels its given ``AsyncCancellable``. public class AnyAsyncCancellable: AsyncCancellable { let canceller: () -> Void - init(cancellable: AC) { + public init(cancellable: AC) { canceller = { cancellable.cancel() } } @@ -14,14 +16,4 @@ public class AnyAsyncCancellable: AsyncCancellable { public func cancel() { canceller() } - - // MARK: Hashable conformance - - public static func == (lhs: AnyAsyncCancellable, rhs: AnyAsyncCancellable) -> Bool { - ObjectIdentifier(lhs) == ObjectIdentifier(rhs) - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) - } } diff --git a/Sources/AsyncMonitor/AsyncCancellable.swift b/Sources/AsyncMonitor/AsyncCancellable.swift index 55252bd..ea7acfe 100644 --- a/Sources/AsyncMonitor/AsyncCancellable.swift +++ b/Sources/AsyncMonitor/AsyncCancellable.swift @@ -1,11 +1,29 @@ -public protocol AsyncCancellable: Hashable { +/// Represents an async operation that can be cancelled. +public protocol AsyncCancellable: AnyObject, Hashable { + /// Cancels the operation. func cancel() + /// Stores this cancellable in the given set, using the type-erasing wrapper ``AnyAsyncCancellable``. This method has a + /// default implementation and you typically shouldn't need to override it. func store(in set: inout Set) } +// MARK: Default implementations + public extension AsyncCancellable { func store(in set: inout Set) { set.insert(AnyAsyncCancellable(cancellable: self)) } } + +// MARK: Hashable conformance + +public extension AsyncCancellable { + static func == (lhs: Self, rhs: Self) -> Bool { + ObjectIdentifier(lhs) == ObjectIdentifier(rhs) + } + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } +} diff --git a/Sources/AsyncMonitor/AsyncMonitor.swift b/Sources/AsyncMonitor/AsyncMonitor.swift index da4b14f..7d30fe0 100644 --- a/Sources/AsyncMonitor/AsyncMonitor.swift +++ b/Sources/AsyncMonitor/AsyncMonitor.swift @@ -1,6 +1,24 @@ +/// A monitor that observes an asynchronous sequence and invokes the given block for each received element. +/// +/// The element must be `Sendable` so to use it to monitor notifications from `NotificationCenter` you'll need to map them to +/// something sendable before calling `monitor` on the sequence. e.g. +/// +/// ``` +/// NotificationCenter.default +/// .notifications(named: .NSCalendarDayChanged).map(\.name) +/// .monitor { _ in whatever() } +/// .store(in: &cancellables) +/// ``` public final class AsyncMonitor: Hashable, AsyncCancellable { let task: Task + /// Creates an ``AsyncMonitor`` that observes the provided asynchronous sequence. + /// + /// - Parameters: + /// - isolation: An optional actor isolation context to inherit. + /// Defaults to `#isolation`, preserving the caller's actor isolation. + /// - sequence: The asynchronous sequence of elements to observe. + /// - block: A closure to execute for each element yielded by the sequence. public init( isolation: isolated (any Actor)? = #isolation, sequence: any AsyncSequence, @@ -21,17 +39,8 @@ public final class AsyncMonitor: Hashable, AsyncCancellable { // MARK: AsyncCancellable conformance + /// Cancels the underlying task monitoring the asynchronous sequence. public func cancel() { task.cancel() } - - // MARK: Hashable conformance - - public static func == (lhs: AsyncMonitor, rhs: AsyncMonitor) -> Bool { - lhs.task == rhs.task - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(task) - } } diff --git a/Sources/AsyncMonitor/AsyncSequence+Extensions.swift b/Sources/AsyncMonitor/AsyncSequence+Extensions.swift index 97a4e02..a2a83ea 100644 --- a/Sources/AsyncMonitor/AsyncSequence+Extensions.swift +++ b/Sources/AsyncMonitor/AsyncSequence+Extensions.swift @@ -1,4 +1,11 @@ public extension AsyncSequence where Element: Sendable, Failure == Never { + /// Observes the elements yielded by this sequence and executes the given closure with each element. + /// + /// This method preserves the actor isolation of the caller by default when `isolation` is not specified. + /// + /// - Parameters: + /// - isolation: An optional actor isolation context to inherit. Defaults to `#isolation`, preserving the caller's actor isolation. + /// - block: A closure that's executed with each yielded element. func monitor( isolation: isolated (any Actor)? = #isolation, _ block: @escaping (Element) async -> Void @@ -6,6 +13,15 @@ public extension AsyncSequence where Element: Sendable, Failure == Never { AsyncMonitor(isolation: isolation, sequence: self, performing: block) } + /// Observes the elements yielded by this sequence and executes the given closure with each element the weakly-captured + /// context object. + /// + /// This method preserves the actor isolation of the caller by default when `isolation` is not specified. + /// + /// - Parameters: + /// - isolation: An optional actor isolation context to inherit. Defaults to `#isolation`, preserving the caller's actor isolation. + /// - context: The object to capture weakly for use within the closure. + /// - block: A closure that's executed with each yielded element, and the `context`. func monitor( isolation: isolated (any Actor)? = #isolation, context: Context, diff --git a/Sources/AsyncMonitor/NSObject+AsyncKVO.swift b/Sources/AsyncMonitor/NSObject+AsyncKVO.swift index dba9c54..9e36c26 100644 --- a/Sources/AsyncMonitor/NSObject+AsyncKVO.swift +++ b/Sources/AsyncMonitor/NSObject+AsyncKVO.swift @@ -3,6 +3,12 @@ public import Foundation extension KeyPath: @unchecked @retroactive Sendable where Value: Sendable {} public extension NSObjectProtocol where Self: NSObject { + /// Observes changes to the specified key path on the object and asynchronously yields each value. Values must be `Sendable`. + /// + /// - Parameters: + /// - keyPath: The key path to observe on this object. The value must be `Sendable`. + /// - options: KVO options to use for observation. Defaults to an empty set. + /// - changeHandler: A closure that's executed with each new value. func values( for keyPath: KeyPath, options: NSKeyValueObservingOptions = [],