Why use NWPathMonitor instead of reachability, and how do you use connectivity information correctly?

12.4.3 · L3 · Interview

Predict first

Does a path monitor with a handler compile as described?

import Network

let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
    let online = path.status == .satisfied
    let costly = path.isExpensive || path.isConstrained
    _ = (online, costly)
}
monitor.start(queue: DispatchQueue(label: "net"))
monitor.cancel()
  • Yes
  • No

Mental model. Path monitoring is the traffic report: “the road is closed” helps you plan, but you still have to drive and see, because the report may be out of date or the road may be fine while the destination is shut.

Old reachability (SCNetworkReachability and third-party wrappers) answered “is there a route to a host?” and encouraged a bad pattern: check, then decide whether to try. NWPathMonitor from the Network framework reports the state of the network path as it changes: path.status (.satisfied, .unsatisfied, .requiresConnection), whether the path isExpensive (cellular or a personal hotspot) or isConstrained (Low Data Mode), which interfaces are available (usesInterfaceType(.wifi)) and whether it supports IPv4 or IPv6 and DNS. You create one, set pathUpdateHandler (or use its AsyncStream of paths in newer SDKs), give it a queue and call start(queue:), keeping a strong reference and calling cancel() when done. The handler fires once at start with the current state and then on every change.

Use it for UI and policy, not as a gate. A .satisfied path does not mean the server is reachable (captive portals, DNS failures, firewalls), and .unsatisfied may flip a moment later; so always attempt the request and handle the error, and use monitoring to show an “offline” banner, to retry when connectivity returns, to postpone large transfers on expensive or constrained paths, and to choose lower-quality media. For requests, the better primitive is URLSessionConfiguration.waitsForConnectivity = true: the system holds the task until a path exists and notifies you through taskIsWaitingForConnectivity, so you do not write the wait yourself. Mark requests that should not use cellular or expensive links with the configuration’s allowsExpensiveNetworkAccess and allowsConstrainedNetworkAccess. Test offline behaviour with the Network Link Conditioner and airplane mode, because simulated stubs never reveal these paths.

The handler receives an NWPath with status and cost flags, and the monitor is started on a queue and cancelled when done.

Common traps

  • Checking reachability before every request and refusing to send when “offline”. The status may be stale or wrong; try and handle the error.
  • Not holding or cancelling the monitor. It stops delivering when released, or leaks if never cancelled.

They might follow up with

  • What do isExpensive and isConstrained mean? Cellular or hotspot, and Low Data Mode; both are reasons to defer large transfers.
  • What replaces a manual wait-until-online loop? waitsForConnectivity = true on the session configuration.

Related questions

  • 12.5.1 How should a client retry failed requests: what to retry, how long to wait, and what idempotency has to do…
  • 12.4.1 How does URLSessionWebSocketTask work, and what do you have to handle yourself?
  • 12.1.1 What do the URLSession configurations control, and how do default, ephemeral and background sessions differ?

← How do you do multipart uploads, large uploads and downloads, and…