From d44e286b0ab2be870d9b43e6ef95a586230dcb1c Mon Sep 17 00:00:00 2001 From: Varun Santhanam Date: Sun, 31 Mar 2024 09:06:22 -0700 Subject: [PATCH 1/2] Throw cancellation errors when async observation is cancelled --- .../NetworkMonitor+Concurrency.swift | 31 ++++++++++++------- .../API/NetworkMonitor/NetworkMonitor.swift | 4 ++- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Sources/NetworkReachability/API/NetworkMonitor/NetworkMonitor+Concurrency.swift b/Sources/NetworkReachability/API/NetworkMonitor/NetworkMonitor+Concurrency.swift index 7b0616b7..fff0897a 100644 --- a/Sources/NetworkReachability/API/NetworkMonitor/NetworkMonitor+Concurrency.swift +++ b/Sources/NetworkReachability/API/NetworkMonitor/NetworkMonitor+Concurrency.swift @@ -31,16 +31,20 @@ public extension NetworkMonitor { /// Retrieve the latest known network path using [Swift Concurrency](https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html) /// /// ```swift - /// func updateReachability() async { - /// let path = await NetworkMonitor.networkPath + /// func updateReachability() async throws { + /// let path = try await NetworkMonitor.networkPath /// // Do something with `path` /// } /// ``` static var networkPath: NWPath { - get async { - await withUnsafeContinuation { continuation in + get async throws { + try await withUnsafeThrowingContinuation { continuation in NetworkMonitor.networkPath { path in - continuation.resume(returning: path) + if Task.isCancelled { + continuation.resume(throwing: CancellationError()) + } else { + continuation.resume(returning: path) + } } } } @@ -55,7 +59,7 @@ public extension NetworkMonitor { /// // Do something with `path` /// } /// ``` - static var networkPathUpdates: AsyncStream { + static var networkPathUpdates: AsyncThrowingStream { stream(.init()) } @@ -68,7 +72,7 @@ public extension NetworkMonitor { /// // Do something with `path` /// } /// ``` - static func networkPathUpdates(requiringInterfaceType interfaceType: NWInterface.InterfaceType) -> AsyncStream { + static func networkPathUpdates(requiringInterfaceType interfaceType: NWInterface.InterfaceType) -> AsyncThrowingStream { stream(.init(requiredInterfaceType: interfaceType)) } @@ -82,16 +86,21 @@ public extension NetworkMonitor { /// } /// ``` @available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) - static func networkPathUpdates(prohibitingInterfaceTypes interfaceTypes: [NWInterface.InterfaceType]) -> AsyncStream { + static func networkPathUpdates(prohibitingInterfaceTypes interfaceTypes: [NWInterface.InterfaceType]) -> AsyncThrowingStream { stream(.init(prohibitedInterfaceTypes: interfaceTypes)) } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -private func stream(_ monitor: NWPathMonitor) -> AsyncStream { - .init(bufferingPolicy: .bufferingNewest(1)) { continuation in +private func stream(_ monitor: NWPathMonitor) -> AsyncThrowingStream { + .init { continuation in monitor.pathUpdateHandler = { path in - continuation.yield(path) + if Task.isCancelled { + monitor.cancel() + continuation.finish(throwing: CancellationError()) + } else { + continuation.yield(path) + } } monitor.start(queue: .networkMonitorQueue) } diff --git a/Sources/NetworkReachability/API/NetworkMonitor/NetworkMonitor.swift b/Sources/NetworkReachability/API/NetworkMonitor/NetworkMonitor.swift index c9636667..ebf06351 100644 --- a/Sources/NetworkReachability/API/NetworkMonitor/NetworkMonitor.swift +++ b/Sources/NetworkReachability/API/NetworkMonitor/NetworkMonitor.swift @@ -346,7 +346,9 @@ public final class NetworkMonitor { } } else if #available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *) { Task { - completionHandler(path) + await MainActor.run { + completionHandler(path) + } } } else { DispatchQueue.main.async { From b3593042f781f1dac50af6dc71121eaeb516b688 Mon Sep 17 00:00:00 2001 From: Varun Santhanam Date: Sun, 31 Mar 2024 09:16:57 -0700 Subject: [PATCH 2/2] Update Unit Tests --- .../NetworkMonitorConcurrencyTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/NetworkReachabilityTests/NetworkMonitorConcurrencyTests.swift b/Tests/NetworkReachabilityTests/NetworkMonitorConcurrencyTests.swift index 1c3fe9b4..89bf4225 100644 --- a/Tests/NetworkReachabilityTests/NetworkMonitorConcurrencyTests.swift +++ b/Tests/NetworkReachabilityTests/NetworkMonitorConcurrencyTests.swift @@ -33,8 +33,8 @@ import XCTest @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) final class NetworkMonitorConcurrencyTests: XCTestCase { - func test_get_concurrency() async { - let path = await NetworkMonitor.networkPath + func test_get_concurrency() async throws { + let path = try await NetworkMonitor.networkPath XCTAssertEqual(path.status, .satisfied) } @@ -42,7 +42,7 @@ final class NetworkMonitorConcurrencyTests: XCTestCase { let expectation = expectation(description: "pass") Task { - for await status in NetworkMonitor.networkPathUpdates.map(\.status) { + for try await status in NetworkMonitor.networkPathUpdates.map(\.status) { if status == .satisfied { expectation.fulfill() }