mirror of
https://github.com/samsonjs/AsyncMonitor.git
synced 2026-04-27 14:57:39 +00:00
Fixes for Swift 6.2 on iOS 26 and macOS 26
This commit is contained in:
parent
33abeebe52
commit
52b10585ab
7 changed files with 25 additions and 18 deletions
|
|
@ -32,8 +32,8 @@ class SimplestVersion {
|
||||||
This example uses the context parameter to avoid reference cycles with `self`.
|
This example uses the context parameter to avoid reference cycles with `self`.
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
class WithContext {
|
final class WithContext: Sendable {
|
||||||
var cancellables = Set<AnyAsyncCancellable>()
|
nonisolated(unsafe) var cancellables = Set<AnyAsyncCancellable>()
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
NotificationCenter.default
|
NotificationCenter.default
|
||||||
|
|
@ -55,7 +55,7 @@ class WithContext {
|
||||||
Working with Combine publishers is trivial thanks to [`AnyPublisher.values`][values].
|
Working with Combine publishers is trivial thanks to [`AnyPublisher.values`][values].
|
||||||
|
|
||||||
```swift
|
```swift
|
||||||
import Combine
|
@preconcurrency import Combine
|
||||||
|
|
||||||
class CombineExample {
|
class CombineExample {
|
||||||
var cancellables = Set<AnyAsyncCancellable>()
|
var cancellables = Set<AnyAsyncCancellable>()
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ public final class AsyncMonitor: Hashable, AsyncCancellable {
|
||||||
/// Defaults to `#isolation`, preserving the caller's actor isolation.
|
/// Defaults to `#isolation`, preserving the caller's actor isolation.
|
||||||
/// - sequence: The asynchronous sequence of elements to observe.
|
/// - sequence: The asynchronous sequence of elements to observe.
|
||||||
/// - block: A closure to execute for each element yielded by the sequence.
|
/// - block: A closure to execute for each element yielded by the sequence.
|
||||||
@available(iOS 18, *)
|
@available(iOS 18, macOS 15, *)
|
||||||
public init<Element: Sendable>(
|
public init<Element: Sendable>(
|
||||||
isolation: isolated (any Actor)? = #isolation,
|
isolation: isolated (any Actor)? = #isolation,
|
||||||
sequence: any AsyncSequence<Element, Never>,
|
sequence: any AsyncSequence<Element, Never>,
|
||||||
|
|
@ -41,6 +41,7 @@ public final class AsyncMonitor: Hashable, AsyncCancellable {
|
||||||
/// - sequence: The asynchronous sequence of elements to observe.
|
/// - sequence: The asynchronous sequence of elements to observe.
|
||||||
/// - block: A closure to execute for each element yielded by the sequence.
|
/// - block: A closure to execute for each element yielded by the sequence.
|
||||||
@available(iOS, introduced: 17, obsoleted: 18)
|
@available(iOS, introduced: 17, obsoleted: 18)
|
||||||
|
@available(macOS, introduced: 14, obsoleted: 15)
|
||||||
public init<Element: Sendable, Sequence>(
|
public init<Element: Sendable, Sequence>(
|
||||||
sequence: sending Sequence,
|
sequence: sending Sequence,
|
||||||
@_inheritActorContext performing block: @escaping @Sendable (Element) async -> Void
|
@_inheritActorContext performing block: @escaping @Sendable (Element) async -> Void
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
@available(iOS 18, *)
|
@available(iOS 18, macOS 15, *)
|
||||||
public extension AsyncSequence where Element: Sendable, Failure == Never {
|
public extension AsyncSequence where Element: Sendable, Failure == Never {
|
||||||
/// Observes the elements yielded by this sequence and executes the given closure with each element.
|
/// Observes the elements yielded by this sequence and executes the given closure with each element.
|
||||||
///
|
///
|
||||||
|
|
@ -36,6 +36,7 @@ public extension AsyncSequence where Element: Sendable, Failure == Never {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS, introduced: 17, obsoleted: 18)
|
@available(iOS, introduced: 17, obsoleted: 18)
|
||||||
|
@available(macOS, introduced: 14, obsoleted: 15)
|
||||||
public extension AsyncSequence where Self: Sendable, Element: Sendable {
|
public extension AsyncSequence where Self: Sendable, Element: Sendable {
|
||||||
/// Observes the elements yielded by this sequence and executes the given closure with each element.
|
/// Observes the elements yielded by this sequence and executes the given closure with each element.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ public extension NSObjectProtocol where Self: NSObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 18, *)
|
@available(iOS 18, macOS 15, *)
|
||||||
public extension NSObjectProtocol where Self: NSObject {
|
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`.
|
/// Observes changes to the specified key path on the object and asynchronously yields each value. Values must be `Sendable`.
|
||||||
///
|
///
|
||||||
|
|
@ -44,6 +44,7 @@ public extension NSObjectProtocol where Self: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS, introduced: 17, obsoleted: 18)
|
@available(iOS, introduced: 17, obsoleted: 18)
|
||||||
|
@available(macOS, introduced: 14, obsoleted: 15)
|
||||||
public extension NSObjectProtocol where Self: NSObject {
|
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`.
|
/// Observes changes to the specified key path on the object and asynchronously yields each value. Values must be `Sendable`.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -49,12 +49,12 @@ class AsyncMonitorTests {
|
||||||
try await Task.sleep(for: .milliseconds(10))
|
try await Task.sleep(for: .milliseconds(10))
|
||||||
}
|
}
|
||||||
|
|
||||||
class Owner {
|
final class Owner: Sendable {
|
||||||
let deinitHook: () -> Void
|
let deinitHook: @Sendable () -> Void
|
||||||
|
|
||||||
private var cancellable: (any AsyncCancellable)?
|
nonisolated(unsafe) private var cancellable: (any AsyncCancellable)?
|
||||||
|
|
||||||
init(center: NotificationCenter, deinitHook: @escaping () -> Void) {
|
init(center: NotificationCenter, deinitHook: @escaping @Sendable () -> Void) {
|
||||||
self.deinitHook = deinitHook
|
self.deinitHook = deinitHook
|
||||||
let name = Notification.Name("irrelevant name")
|
let name = Notification.Name("irrelevant name")
|
||||||
cancellable = center.notifications(named: name)
|
cancellable = center.notifications(named: name)
|
||||||
|
|
@ -78,8 +78,10 @@ class AsyncMonitorTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class SendableObject: NSObject, Sendable {}
|
||||||
|
|
||||||
@Test func stopsCallingBlockWhenContextIsDeallocated() async throws {
|
@Test func stopsCallingBlockWhenContextIsDeallocated() async throws {
|
||||||
var context: NSObject? = NSObject()
|
var context: SendableObject? = SendableObject()
|
||||||
subject = center.notifications(named: name)
|
subject = center.notifications(named: name)
|
||||||
.map(\.name)
|
.map(\.name)
|
||||||
.monitor(context: context!) { context, receivedName in
|
.monitor(context: context!) { context, receivedName in
|
||||||
|
|
|
||||||
|
|
@ -9,22 +9,22 @@ class AsyncKVOTests {
|
||||||
@Test(.timeLimit(.minutes(1)))
|
@Test(.timeLimit(.minutes(1)))
|
||||||
func monitorValuesYieldsChanges() async throws {
|
func monitorValuesYieldsChanges() async throws {
|
||||||
let subject = try #require(subject)
|
let subject = try #require(subject)
|
||||||
var values = [Double]()
|
let values = ValueLocker(value: [Double]())
|
||||||
let total = 3
|
let total = 3
|
||||||
cancellable = subject.values(for: \.fractionCompleted)
|
cancellable = subject.values(for: \.fractionCompleted)
|
||||||
.prefix(total)
|
.prefix(total)
|
||||||
.monitor { progress in
|
.monitor { progress in
|
||||||
values.append(progress)
|
values.modify { $0.append(progress) }
|
||||||
}
|
}
|
||||||
|
|
||||||
for n in 1...total {
|
for n in 1...total {
|
||||||
subject.completedUnitCount += 1
|
subject.completedUnitCount += 1
|
||||||
while values.count < n {
|
while values.value.count < n {
|
||||||
try await Task.sleep(for: .microseconds(2))
|
try await Task.sleep(for: .microseconds(2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#expect(values.count == total)
|
#expect(values.value.count == total)
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's important that the test and the progress-observing task are not on the same actor, so
|
// It's important that the test and the progress-observing task are not on the same actor, so
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import Foundation
|
||||||
|
|
||||||
// MARK: Basics
|
// MARK: Basics
|
||||||
|
|
||||||
|
extension Notification: @unchecked @retroactive Sendable {}
|
||||||
|
|
||||||
class SimplestVersion {
|
class SimplestVersion {
|
||||||
let cancellable = NotificationCenter.default
|
let cancellable = NotificationCenter.default
|
||||||
.notifications(named: .NSCalendarDayChanged)
|
.notifications(named: .NSCalendarDayChanged)
|
||||||
|
|
@ -12,8 +14,8 @@ class SimplestVersion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class WithContext {
|
final class WithContext: Sendable {
|
||||||
var cancellables = Set<AnyAsyncCancellable>()
|
nonisolated(unsafe) var cancellables = Set<AnyAsyncCancellable>()
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
NotificationCenter.default
|
NotificationCenter.default
|
||||||
|
|
@ -31,7 +33,7 @@ class WithContext {
|
||||||
|
|
||||||
// MARK: - Combine
|
// MARK: - Combine
|
||||||
|
|
||||||
import Combine
|
@preconcurrency import Combine
|
||||||
|
|
||||||
class CombineExample {
|
class CombineExample {
|
||||||
var cancellables = Set<AnyAsyncCancellable>()
|
var cancellables = Set<AnyAsyncCancellable>()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue