原文:Native Network Monitoring In Swift
此api支持 iOS12+
我们将介绍使用Swift 5在iOS上监控网络连接状态的原生解决方案,以及如何使用Network Link Conditioner。
你将会发现要监听设备网络连接状态,大部分都是依赖第三方框架,如Reachability、Alamofire的NetworkReachabilityManager
,或建议您创建一个定期尝试提出HTTP请求的实用程序,以确定网络连接状态。
相反,我将提供另一种方法,利用iOS 12中引入的不太为人所知的原生框架。
对于这个实现,我们只需要苹果的Network
框架——它就驱动URLSession
的同一框架。虽然当您需要直接访问自定义应用程序协议的TLS、TCP和UDP等协议时,您通常会使用此框架,但我们在这里不会做任何事情。
实现初始化
让我们从创建NetworkMonitor
类开始:
import Network
final class NetworkMonitor {
static let shared = NetworkMonitor()
private let monitor: NWPathMonitor
private init() {
monitor = NWPathMonitor()
}
}
NWPathMonitor
是一个观察者,我们可以用它来监控和响应网络变化。
接下来,我们需要创建一些属性来存储网络连接的当前状态:
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()
}
}
我们只需要这些属性是只读的,所以我们在这里选择了
private(set)
)。
我们显然不希望这个长期运行的任务发生在我们应用程序的主线程上,所以让我们创建一个新的DispatchQueue
来管理这项工作:
private let queue = DispatchQueue(label: "NetworkConnectivityMonitor")
Network
框架定义了一个名为NWInterface.InterfaceType
的enum
,该枚举指定了我们的设备可以支持的所有不同媒体类型(WiFi、蜂窝网络、有线以太网等)。
由于此enum
在ObjC中声明,我们无法像在Swift中声明enums
那样访问allCases
属性。因此,我添加了CaseIterable
协议的一致性,并在这里实现了allCases
。由于这个额外的步骤,我们的其余实现将更加简单和可读。
extension NWInterface.InterfaceType: CaseIterable {
public static var allCases: [NWInterface.InterfaceType] = [
.other,
.wifi,
.cellular,
.loopback,
.wiredEthernet
]
}
我们实施的最后一步是创建负责启动和停止监控过程的功能:
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()
}
网络监视动作
只需调用NetworkMonitor.shared.startMonitoring()
,即可从代码中的任何地方开始监控,尽管在大多数情况下,您都希望在AppDelegate
中启动此过程。然后,我们可以使用NetworkMonitor.shared.isConnected
实时检查我们的网络连接状态。
以下是我们迄今为止的执行情况:
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()
}
}
添加通知支持
当设备的网络连接失败时,iOS应用程序的行为发生了巨大变化——一些屏幕可能会显示设备失去连接、应用程序的缓存行为发生变化或某些用户流完全消失的通知。
为了支持此类行为,我们需要扩展我们的实施范围,以便在连接状态发生变化时发送全应用程序范围的通知。
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")
}
}
}
您可以在这里找到这个项目的源代码。
Network Link Conditioner 使用
鉴于我们已经在讨论网络和调试连接问题,现在似乎是提及Network Link Confitioner 工具最好机会。
使用此工具,您可以在计算机上模拟不同的网络条件,从而在iOS模拟器上模拟。使用此工具,我们不仅可以监控完全在线或离线的极端情况,还可以根据各种网络条件测试应用程序的行为。
您可以从 Apple Developer 网站或点按此处下载。
网络链接条件器位于您的“系统偏好设置”中
如果您对有关iOS开发和Swift的更多文章感兴趣,请查看我的YouTube频道或在Twitter上关注我。