A modern, type-safe, actor-based network path monitoring utility for Apple platforms.
NetworkPathMonitor provides an easy and safe way to observe network connectivity changes using Swift Concurrency, AsyncStream, callbacks, and notifications.
Need real network connectivity testing? Check out NetworkConnectivityKit - performs actual HTTP requests to detect true internet connectivity, including captive portals.
- π¦ Real-time network status monitoring based on
NWPathMonitor
- π§βπ» Actor isolation for thread safety (Swift Concurrency)
- π AsyncStream support for async/await style observation
- ποΈ Callback and NotificationCenter support
- β³ Debounce mechanism to avoid frequent updates
- π Rich NetworkPath with sequence tracking and update reasons
- π οΈ Simple API, easy integration
- Swift 5.10 or later
- iOS 13.0+, macOS 10.15+, tvOS 13.0+, watchOS 6.0+, visionOS 1.0+
Add the following to your Package.swift
:
.package(url: "https://github.com/codingiran/NetworkPathMonitor.git", from: "0.1.5")
Or use Xcode:
File > Add Packages...
and enter the repository URL.
import NetworkPathMonitor
let monitor = NetworkPathMonitor()
await monitor.fire()
// Check current status
let isConnected = await monitor.isPathSatisfied
// Stop monitoring
await monitor.invalidate()
import NetworkPathMonitor
let monitor = NetworkPathMonitor()
await monitor.fire()
Task {
for await path in await monitor.pathUpdates {
print("Network status changed: \(path.status)")
print("Is first update: \(path.isFirstUpdate)")
print("Update reason: \(path.updateReason)")
}
}
import NetworkPathMonitor
let monitor = NetworkPathMonitor()
await monitor.pathOnChange { path in
print("Network changed: \(path.status)")
print("Is first update: \(path.isFirstUpdate)")
print("Update reason: \(path.updateReason)")
}
await monitor.fire()
import NetworkPathMonitor
let observer = NotificationCenter.default.addObserver(
forName: NetworkPathMonitor.networkStatusDidChangeNotification,
object: nil,
queue: .main
) { notification in
if let newPath = notification.userInfo?["newPath"] as? NetworkPath {
print("Network status changed to: \(newPath.status)")
print("Is first update: \(newPath.isFirstUpdate)")
print("Update reason: \(newPath.updateReason)")
}
}
You can set a debounce interval to avoid frequent updates:
let monitor = NetworkPathMonitor(debounceInterval: .seconds(1.0)) // 1 second debounce
NetworkPathMonitor uses a convenient Interval
enum for specifying debounce intervals:
// Different interval types
let monitor1 = NetworkPathMonitor(debounceInterval: .nanoseconds(500_000_000)) // 0.5 seconds
let monitor2 = NetworkPathMonitor(debounceInterval: .milliseconds(500)) // 0.5 seconds
let monitor3 = NetworkPathMonitor(debounceInterval: .seconds(0.5)) // 0.5 seconds
let monitor4 = NetworkPathMonitor(debounceInterval: .minutes(1)) // 1 minute
let monitor5 = NetworkPathMonitor(debounceInterval: .hours(1)) // 1 hour
NetworkPathMonitor now provides rich sequence tracking capabilities through the NetworkPath
type. Each path update includes sequence information and update reasons:
let monitor = NetworkPathMonitor()
await monitor.fire()
Task {
for await path in await monitor.pathUpdates {
// Check if this is the first update after initial connection
if path.isFirstUpdate {
print("First network update received")
}
// Access the previous path for comparison
if let previousPath = path.sequence.previousPath {
print("Previous status: \(previousPath.status)")
print("Previous interfaces: \(previousPath.usedInterfaces.names)")
}
// Get update reason
switch path.updateReason {
case .initial:
print("Initial path when monitor started")
case .physicalChange:
print("Physical network interface changed")
case .uncertain:
print("Network status changed for uncertain reason")
}
}
}
.initial
: The path is the initial path when the monitor is started.physicalChange
: The path has changed due to a physical interface change (e.g., switching from WiFi to Cellular).uncertain
: The reason for the update is uncertain (e.g., network configuration changes)
Each path update has a sequence index that increments with each update:
print("Sequence index: \(path.sequence.index)")
print("Is initial path: \(path.sequence.isInitial)")
init(queue: DispatchQueue = ..., debounceInterval: Interval = .zero)
queue
: The dispatch queue for the underlying NWPathMonitor.debounceInterval
: Debounce interval using convenient Interval enum. Default is .zero (no debounce).
isActive
: Whether monitoring is active.currentPath
: The latestNetworkPath
.isPathSatisfied
: Whether the current path is satisfied (connected).
fire()
: Start monitoring.invalidate()
: Stop monitoring.pathOnChange(_:)
: Register a callback for path changes.pathUpdates
: AsyncStream of NetworkPath updates.
MIT License. See LICENSE for details.
Contributions are welcome! Please open issues or submit pull requests.