原文:Native Network Monitoring In Swift
This api supports iOS12+
We will introduce a native solution to monitor network connection status on iOS using Swift 5, and how to use Network Link Conditioner.
You will find that to monitor the device network connection status, most of them rely on third-party frameworks, such as Reachability , Alamofire NetworkReachabilityManager
, or suggest that you create a utility that periodically tries to make HTTP requests to determine the network connection status.
Instead, I'll provide another approach that leverages lesser-known native frameworks introduced in iOS 12.
For this implementation, we only need Apple's Network
framework - URLSession
the same framework it drives. While you would typically use this framework when you need direct access to protocols like TLS, TCP, and UDP for custom application protocols, we won't do anything here.
implement initialization
Let's NetworkMonitor
start by creating the class:
import Network
final class NetworkMonitor {
static let shared = NetworkMonitor()
private let monitor: NWPathMonitor
private init() {
monitor = NWPathMonitor()
}
}
NWPathMonitor
is an observer that we can use to monitor and respond to network changes.
Next, we need to create some properties to store the current state of the network connection:
final class NetworkMonitor {
static let shared = NetworkMonitor()
private let monitor: NWPathMonitor
private(set) var isConnected = false
/// Checks if the path uses an NWInterface that is considered to
/// be expensive
///
/// Cellular interfaces are considered expensive. WiFi hotspots
/// from an iOS device are considered expensive. Other
/// interfaces may appear as expensive in the future.
private(set) var isExpensive = false
/// Interface types represent the underlying media for
/// a network link
///
/// This can either be `other`, `wifi`, `cellular`,
/// `wiredEthernet`, or `loopback`
private(set) var currentConnectionType: NWInterface.InterfaceType?
private init() {
monitor = NWPathMonitor()
}
}
We just need these properties to be read-only, so we choose that here
private(set)
).
We obviously don't want this long-running task to happen on our application's main thread, so let's create a new one DispatchQueue
to manage this work:
private let queue = DispatchQueue(label: "NetworkConnectivityMonitor")
Network
The framework defines an enum named NWInterface.InterfaceType
, enum
which specifies all the different media types that our device can support (WiFi, Cellular, Wired Ethernet, etc.).
Since this enum
is declared in ObjC, we can't enums
access allCases
the property like it is declared in Swift. So I added CaseIterable
protocol conformance and implemented it here allCases
. The rest of our implementation will be simpler and more readable because of this extra step.
extension NWInterface.InterfaceType: CaseIterable {
public static var allCases: [NWInterface.InterfaceType] = [
.other,
.wifi,
.cellular,
.loopback,
.wiredEthernet
]
}
The last step in our implementation is to create the functions responsible for starting and stopping the monitoring process:
func startMonitoring() {
monitor.pathUpdateHandler = {
[weak self] path in
self?.isConnected = path.status != .unsatisfied
self?.isExpensive = path.isExpensive
// Identifies the current connection type from the
// list of potential network link types
self?.currentConnectionType = NWInterface.InterfaceType.allCases.filter {
path.usesInterfaceType($0) }.first
}
monitor.start(queue: queue)
}
func stopMonitoring() {
monitor.cancel()
}
network monitoring action
NetworkMonitor.shared.startMonitoring()
You can start monitoring from anywhere in your code by simply calling , although in most cases you'll want to AppDelegate
start the process in . We can then NetworkMonitor.shared.isConnected
check the status of our network connection in real time using
Here's our implementation so far:
import Network
extension NWInterface.InterfaceType: CaseIterable {
public static var allCases: [NWInterface.InterfaceType] = [
.other,
.wifi,
.cellular,
.loopback,
.wiredEthernet
]
}
final class NetworkMonitor {
static let shared = NetworkMonitor()
private let queue = DispatchQueue(label: "NetworkConnectivityMonitor")
private let monitor: NWPathMonitor
private(set) var isConnected = false
private(set) var isExpensive = false
private(set) var currentConnectionType: NWInterface.InterfaceType?
private init() {
monitor = NWPathMonitor()
}
func startMonitoring() {
monitor.pathUpdateHandler = {
[weak self] path in
self?.isConnected = path.status != .unsatisfied
self?.isExpensive = path.isExpensive
self?.currentConnectionType = NWInterface.InterfaceType.allCases.filter {
path.usesInterfaceType($0) }.first
}
monitor.start(queue: queue)
}
func stopMonitoring() {
monitor.cancel()
}
}
Add notification support
When a device's network connection fails, the behavior of an iOS app changes dramatically -- some screens may display notifications that the device has lost connectivity, that the app's caching behavior has changed, or that certain user streams have disappeared entirely.
To support such behavior, we need to extend our implementation to send application-wide notifications when the connection state changes.
import Foundation
import Network
extension Notification.Name {
static let connectivityStatus = Notification.Name(rawValue: "connectivityStatusChanged")
}
extension NWInterface.InterfaceType: CaseIterable {
public static var allCases: [NWInterface.InterfaceType] = [
.other,
.wifi,
.cellular,
.loopback,
.wiredEthernet
]
}
final class NetworkMonitor {
static let shared = NetworkMonitor()
private let queue = DispatchQueue(label: "NetworkConnectivityMonitor")
private let monitor: NWPathMonitor
private(set) var isConnected = false
private(set) var isExpensive = false
private(set) var currentConnectionType: NWInterface.InterfaceType?
private init() {
monitor = NWPathMonitor()
}
func startMonitoring() {
monitor.pathUpdateHandler = {
[weak self] path in
self?.isConnected = path.status != .unsatisfied
self?.isExpensive = path.isExpensive
self?.currentConnectionType = NWInterface.InterfaceType.allCases.filter {
path.usesInterfaceType($0) }.first
NotificationCenter.default.post(name: .connectivityStatus, object: nil)
}
monitor.start(queue: queue)
}
func stopMonitoring() {
monitor.cancel()
}
}
// ViewController.swift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(showOfflineDeviceUI(notification:)), name: NSNotification.Name.connectivityStatus, object: nil)
}
@objc func showOfflineDeviceUI(notification: Notification) {
if NetworkMonitor.shared.isConnected {
print("Connected")
} else {
print("Not connected")
}
}
}
You can find the source code for this project here .
Using Network Link Conditioner
Given that we've already been talking about networking and debugging connection issues, now seems like a good time to mention the Network Link Confinifier tool.
Using this tool, you can simulate different network conditions on your computer and thus on the iOS Simulator. Using this tool, we can not only monitor the corner cases of being completely online or offline, but also test the behavior of the application according to various network conditions.
You can download it from the Apple Developer website or click here .
The Network Link Conditioner is located in your System Preferences
If you're interested in more articles about iOS development and Swift, check out my YouTube channel or follow me on Twitter .