logicito
logicito

Reputation: 225

Use NWPathMonitor with Swift Modern Concurrency (AsyncStream) vs GCD (DispatchQueue)

I have noticed that the start(queue:) method in NWPathMonitor requires a queue of type DispatchQueue. Is there a way to implement this using Swift Modern Concurrency, probably using AsyncStream?

Using Apple documentation for AsyncStream, I have created the extension to NWPathMonitor, but I cannot start the NWPathMonitor monitor, any suggestion will be appreciated, thanks

extension NWPathMonitor {
  static var nwpath: AsyncStream<NWPath> {
    AsyncStream { continuation in
      let monitor = NWPathMonitor()
      monitor.pathUpdateHandler = { path in
        continuation.yield(path)
      }
      continuation.onTermination = { @Sendable _ in
        monitor.cancel()
      }
      // monitor.start(queue: )
    }
  }
}

Read Apple's documentation

Upvotes: 7

Views: 1953

Answers (2)

Rob
Rob

Reputation: 437552

In iOS 17, macOS 14, etc., and later, NWPathMonitor conforms to AsyncSequence:

func startMonitoring() async {
    let monitor = NWPathMonitor()
    for await path in monitor {
        print(path.debugDescription)
    }
}

In earlier OS versions, you can wrap it in an AsyncStream and supply it a queue.

So:

extension NWPathMonitor {
    func paths() -> AsyncStream<NWPath> {
        AsyncStream { continuation in
            pathUpdateHandler = { path in
                continuation.yield(path)
            }
            continuation.onTermination = { [weak self] _ in
                self?.cancel()
            }
            start(queue: DispatchQueue(label: "NSPathMonitor.paths"))
        }
    }
}

Then you can do things like:

func startMonitoring() async {
    let monitor = NWPathMonitor()
    for await path in monitor.paths() {
        print(path.debugDescription)
    }
}

A few unrelated and stylistic recommendations, which I integrated in the above:

  1. I did not make this static, as we generally want our extensions to be as flexible as possible. If this is in an extension, we want the application developer to create whatever NWPathMonitor they want (e.g., perhaps requiring or prohibiting certain interfaces) and then create the asynchronous sequence for the updates for whatever path monitor they want.

  2. I made this a function, rather than a computed property, so that it is intuitive to an application developer that this will create a new sequence every time you call it. I would advise against hiding factories behind computed properties.

    The concern with a computed property is that it is not at all obvious to an application developer unfamiliar with the underlying implementation that if you access the same property twice that you will get two completely different objects. Using a method makes this a little more explicit.

Obviously, you are free to do whatever you want regarding these two observations, but I at least wanted to explain my rationale for the adjustments in the above code.

Upvotes: 14

malhal
malhal

Reputation: 30575

NWPathMonitor already is an async sequence, use like this:

.task {
    let monitor = NWPathMonitor()
    for await path in monitor {
        // path is NWPath struct
        print("Status \(path.status)")
    }
}

Upvotes: 0

Related Questions