diff --git a/Changelog.md b/Changelog.md index 911f3f2..1f5adaa 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,7 +2,12 @@ ## [Unreleased] -- Your change here. +### Removed +- **Breaking**: Dropped support for iOS 17 and macOS 14. Minimum platforms are now iOS 18.0 and macOS 15.0. +- Removed the iOS 17 compatibility variants of `AsyncMonitor.init`, `AsyncSequence.monitor`, and `NSObjectProtocol.monitorValues` along with their `@Sendable` closure requirements. + +### Changed +- The remaining `monitor` and `monitorValues` APIs no longer carry `@available` gates and rely on the actor-isolation-aware overloads that previously required iOS 18 / macOS 15. [Unreleased]: https://github.com/samsonjs/AsyncMonitor/compare/0.3.1...HEAD diff --git a/Package.swift b/Package.swift index 47a0456..088b576 100644 --- a/Package.swift +++ b/Package.swift @@ -5,8 +5,8 @@ import PackageDescription let package = Package( name: "AsyncMonitor", platforms: [ - .iOS(.v17), - .macOS(.v14), + .iOS(.v18), + .macOS(.v15), ], products: [ .library( diff --git a/Readme.md b/Readme.md index d256945..a4457f1 100644 --- a/Readme.md +++ b/Readme.md @@ -100,7 +100,7 @@ The only way to install this package is with Swift Package Manager (SPM). Please ### Supported Platforms -This package is supported on iOS 17.0+ and macOS 14.0+. +This package is supported on iOS 18.0+ and macOS 15.0+. ### Xcode diff --git a/Sources/AsyncMonitor/AsyncMonitor.docc/AsyncMonitor.md b/Sources/AsyncMonitor/AsyncMonitor.docc/AsyncMonitor.md index a362cef..6680c8a 100644 --- a/Sources/AsyncMonitor/AsyncMonitor.docc/AsyncMonitor.md +++ b/Sources/AsyncMonitor/AsyncMonitor.docc/AsyncMonitor.md @@ -4,7 +4,7 @@ Wraps async sequence observation in manageable tasks. ## Overview -AsyncMonitor wraps async sequence observation in a `Task` that can be cancelled and stored. It preserves actor isolation on iOS 18+ and includes KVO integration. +AsyncMonitor wraps async sequence observation in a `Task` that can be cancelled and stored. It preserves actor isolation and includes KVO integration. ## Basic Usage @@ -91,7 +91,7 @@ sequence.monitor { element in ## Platform Requirements -- iOS 17.0+ / macOS 14.0+ +- iOS 18.0+ / macOS 15.0+ - Swift 6.0+ ## Topics diff --git a/Sources/AsyncMonitor/AsyncMonitor.swift b/Sources/AsyncMonitor/AsyncMonitor.swift index 3f7f9d0..769ba48 100644 --- a/Sources/AsyncMonitor/AsyncMonitor.swift +++ b/Sources/AsyncMonitor/AsyncMonitor.swift @@ -10,18 +10,17 @@ /// .monitor { _ in print("Day changed!") } /// ``` /// -/// On iOS 18+, preserves the caller's actor isolation context by default. +/// Preserves the caller's actor isolation context by default. /// public final class AsyncMonitor: Hashable, AsyncCancellable { let task: Task - /// Creates an ``AsyncMonitor`` that observes the provided asynchronous sequence with actor isolation support (iOS 18+). + /// Creates an ``AsyncMonitor`` that observes the provided asynchronous sequence. /// /// - Parameters: /// - isolation: An optional actor isolation context to inherit. Defaults to `#isolation`. /// - sequence: The asynchronous sequence of elements to observe. /// - block: A closure to execute for each element yielded by the sequence. - @available(iOS 18, macOS 15, *) public init( isolation: isolated (any Actor)? = #isolation, sequence: any AsyncSequence, @@ -36,13 +35,12 @@ public final class AsyncMonitor: Hashable, AsyncCancellable { } } - /// Creates an ``AsyncMonitor`` for sequences that may throw errors (iOS 18+). + /// Creates an ``AsyncMonitor`` for sequences that may throw errors. /// /// - Parameters: /// - isolation: An optional actor isolation context to inherit. Defaults to `#isolation`. /// - sequence: The asynchronous sequence of elements to observe. May throw errors. /// - block: A closure to execute for each element yielded by the sequence. - @available(iOS 18, macOS 15, *) public init( isolation: isolated (any Actor)? = #isolation, sequence: Sequence, @@ -61,30 +59,6 @@ public final class AsyncMonitor: Hashable, AsyncCancellable { } } - /// Creates an ``AsyncMonitor`` for iOS 17 compatibility. - /// - /// - Parameters: - /// - sequence: The asynchronous sequence of elements to observe. Must be `Sendable`. - /// - block: A `@Sendable` closure to execute for each element yielded by the sequence. - @available(iOS, introduced: 17, obsoleted: 18) - @available(macOS, introduced: 14, obsoleted: 15) - public init( - sequence: sending Sequence, - @_inheritActorContext performing block: @escaping @Sendable (Element) async -> Void - ) where Sequence: AsyncSequence & Sendable, Sequence.Element == Element { - self.task = Task { - do { - for try await element in sequence { - await block(element) - } - } catch { - guard !Task.isCancelled else { return } - - print("Error iterating over sequence: \(error)") - } - } - } - deinit { cancel() } diff --git a/Sources/AsyncMonitor/AsyncSequence+Extensions.swift b/Sources/AsyncMonitor/AsyncSequence+Extensions.swift index 3bf8273..3b6a097 100644 --- a/Sources/AsyncMonitor/AsyncSequence+Extensions.swift +++ b/Sources/AsyncMonitor/AsyncSequence+Extensions.swift @@ -1,4 +1,3 @@ -@available(iOS 18, macOS 15, *) public extension AsyncSequence where Element: Sendable, Failure == Never { /// Observes the elements yielded by this sequence and executes the given closure with each element. /// @@ -84,7 +83,6 @@ public extension AsyncSequence where Element: Sendable, Failure == Never { } } -@available(iOS 18, macOS 15, *) public extension AsyncSequence where Element: Sendable { /// Observes the elements yielded by this sequence and executes the given closure with each element. /// @@ -163,80 +161,3 @@ public extension AsyncSequence where Element: Sendable { } } } - -@available(iOS, introduced: 17, obsoleted: 18) -@available(macOS, introduced: 14, obsoleted: 15) -public extension AsyncSequence where Self: Sendable, Element: Sendable { - /// Observes the elements yielded by this sequence and executes the given closure with each element (iOS 17 compatibility). - /// - /// This method provides backward compatibility for iOS 17. It requires both the sequence and its elements - /// to be `Sendable`, and uses a `@Sendable` closure for thread safety. - /// - /// - Parameters: - /// - block: A `@Sendable` closure that's executed with each yielded element. - /// - /// - Returns: An ``AsyncMonitor`` that can be stored and cancelled as needed. - /// - /// ## Example - /// - /// ```swift - /// let cancellable = sendableAsyncSequence.monitor { element in - /// print("Received: \(element)") - /// } - /// - /// // Store for automatic cleanup - /// cancellable.store(in: &cancellables) - /// ``` - /// - /// - Note: This method is deprecated in iOS 18+ in favour of ``monitor(isolation:_:)`` - /// which provides better actor isolation support. - func monitor( - _ block: @escaping @Sendable (Element) async -> Void - ) -> AsyncMonitor { - AsyncMonitor(sequence: self, performing: block) - } - - /// Observes the elements yielded by this sequence and executes the given closure with each element and the weakly-captured context object (iOS 17 compatibility). - /// - /// This method provides backward compatibility for iOS 17 with weak reference handling to prevent retain cycles. - /// It requires the context to be both `AnyObject` and `Sendable` for thread safety. - /// - /// - Parameters: - /// - context: The object to capture weakly for use within the closure. Must be `Sendable` and will be - /// captured weakly to prevent retain cycles. - /// - block: A `@Sendable` closure that's executed with the weakly-captured context and each yielded element. - /// - /// - Returns: An ``AsyncMonitor`` that can be stored and cancelled as needed. - /// - /// ## Example - /// - /// ```swift - /// class SendableDataManager: Sendable { - /// var cancellables: Set = [] - /// - /// func startMonitoring() { - /// // Context is weakly captured, preventing retain cycle - /// sendableDataStream - /// .monitor(context: self) { manager, data in - /// manager.process(data) - /// }.store(in: &cancellables) - /// } - /// - /// func process(_ data: Data) { - /// // Process the data - /// } - /// } - /// ``` - /// - /// - Note: This method is deprecated in iOS 18+ in favour of ``monitor(isolation:context:_:)`` - /// which provides better actor isolation support. - func monitor( - context: Context, - _ block: @escaping @Sendable (Context, Element) async -> Void - ) -> AsyncMonitor { - AsyncMonitor(sequence: self) { [weak context] element in - guard let context else { return } - await block(context, element) - } - } -} diff --git a/Sources/AsyncMonitor/NSObject+AsyncKVO.swift b/Sources/AsyncMonitor/NSObject+AsyncKVO.swift index 3ba3fe8..639009b 100644 --- a/Sources/AsyncMonitor/NSObject+AsyncKVO.swift +++ b/Sources/AsyncMonitor/NSObject+AsyncKVO.swift @@ -54,7 +54,6 @@ public extension NSObjectProtocol where Self: NSObject { } } -@available(iOS 18, macOS 15, *) public extension NSObjectProtocol where Self: NSObject { /// Observes changes to the specified key path on the object and executes a handler for each change. /// @@ -108,45 +107,3 @@ public extension NSObjectProtocol where Self: NSObject { } } -@available(iOS, introduced: 17, obsoleted: 18) -@available(macOS, introduced: 14, obsoleted: 15) -public extension NSObjectProtocol where Self: NSObject { - /// Observes changes to the specified key path on the object and executes a handler for each change (iOS 17 compatibility). - /// - /// This method provides backward compatibility for iOS 17. It combines KVO observation with ``AsyncMonitor`` - /// and requires a `@Sendable` closure for thread safety. - /// - /// - Parameters: - /// - keyPath: The key path to observe on this object. The value type must be `Sendable` - /// to ensure thread safety across async contexts. - /// - options: KVO options to use for observation. Defaults to an empty set. - /// See `NSKeyValueObservingOptions` for available options. - /// - changeHandler: A `@Sendable` closure that's executed with each new value. - /// - /// - Returns: An ``AsyncCancellable`` that can be stored and cancelled as needed. - /// - /// ## Example - /// - /// ```swift - /// class ProgressObserver { - /// var cancellables: Set = [] - /// - /// func observeProgress(_ progress: Progress) { - /// progress.monitorValues(for: \.fractionCompleted) { fraction in - /// print("Progress: \(fraction.formatted(.percent))") - /// }.store(in: &cancellables) - /// } - /// } - /// ``` - /// - /// - Note: This method is deprecated in iOS 18+ in favour of the non-`@Sendable` version - /// which provides better actor isolation support. - func monitorValues( - for keyPath: KeyPath, - options: NSKeyValueObservingOptions = [], - changeHandler: @escaping @Sendable (Value) -> Void - ) -> any AsyncCancellable { - values(for: keyPath, options: options) - .monitor(changeHandler) - } -} diff --git a/Tests/AsyncMonitorTests/ReadmeExamples.swift b/Tests/AsyncMonitorTests/ReadmeExamples.swift index 481c4ea..8781e58 100644 --- a/Tests/AsyncMonitorTests/ReadmeExamples.swift +++ b/Tests/AsyncMonitorTests/ReadmeExamples.swift @@ -3,8 +3,6 @@ import Foundation // MARK: Basics -extension Notification: @unchecked @retroactive Sendable {} - class SimplestVersion { let cancellable = NotificationCenter.default .notifications(named: .NSCalendarDayChanged)