Drop iOS 17 / macOS 14 support

This commit is contained in:
Sami Samhuri 2026-05-17 11:02:20 -07:00
parent c2a231a40f
commit 6ec19f4c25
8 changed files with 14 additions and 159 deletions

View file

@ -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

View file

@ -5,8 +5,8 @@ import PackageDescription
let package = Package(
name: "AsyncMonitor",
platforms: [
.iOS(.v17),
.macOS(.v14),
.iOS(.v18),
.macOS(.v15),
],
products: [
.library(

View file

@ -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

View file

@ -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

View file

@ -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<Void, Never>
/// 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<Element: Sendable>(
isolation: isolated (any Actor)? = #isolation,
sequence: any AsyncSequence<Element, Never>,
@ -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<Element: Sendable, Sequence: AsyncSequence>(
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<Element: Sendable, Sequence>(
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()
}

View file

@ -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<AnyAsyncCancellable> = []
///
/// 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: AnyObject & Sendable>(
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)
}
}
}

View file

@ -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<AnyAsyncCancellable> = []
///
/// 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<Value: Sendable>(
for keyPath: KeyPath<Self, Value>,
options: NSKeyValueObservingOptions = [],
changeHandler: @escaping @Sendable (Value) -> Void
) -> any AsyncCancellable {
values(for: keyPath, options: options)
.monitor(changeHandler)
}
}

View file

@ -3,8 +3,6 @@ import Foundation
// MARK: Basics
extension Notification: @unchecked @retroactive Sendable {}
class SimplestVersion {
let cancellable = NotificationCenter.default
.notifications(named: .NSCalendarDayChanged)