5 changed files with 352 additions and 86 deletions
@ -0,0 +1,179 @@ |
|||||
|
import Foundation |
||||
|
import SystemConfiguration |
||||
|
import Network |
||||
|
|
||||
|
/// 网络状态监听器(iOS) |
||||
|
public final class NetworkStateMonitor { |
||||
|
private let tag = "NetworkStateMonitor" |
||||
|
|
||||
|
// iOS 12+ |
||||
|
private var monitor: NWPathMonitor? |
||||
|
private let monitorQueue = DispatchQueue(label: "NetworkStateMonitor.queue") |
||||
|
|
||||
|
// iOS 10–11 回退 |
||||
|
private var reachability: SCNetworkReachability? |
||||
|
private var reachabilityQueue: DispatchQueue? |
||||
|
|
||||
|
private(set) var isNetworkAvailable: Bool = true |
||||
|
private weak var networkStateListener: NetworkStateListener? |
||||
|
|
||||
|
/// 网络状态监听接口 |
||||
|
public protocol NetworkStateListener: AnyObject { |
||||
|
func onNetworkAvailable() |
||||
|
func onNetworkLost() |
||||
|
} |
||||
|
|
||||
|
public init() {} |
||||
|
|
||||
|
/// 初始化网络状态监听 |
||||
|
public func initialize(listener: NetworkStateListener? = nil) { |
||||
|
networkStateListener = listener |
||||
|
initNetworkMonitoring() |
||||
|
} |
||||
|
|
||||
|
/// 检查当前网络状态 |
||||
|
/// - Returns: true 表示网络可用 |
||||
|
public func checkNetworkStatus() -> Bool { |
||||
|
if #available(iOS 12.0, *) { |
||||
|
// NWPathMonitor 只在回调里更新状态,因此这里返回缓存的 isNetworkAvailable |
||||
|
return isNetworkAvailable |
||||
|
} else { |
||||
|
// 使用 Reachability 即时检测 |
||||
|
var zeroAddress = sockaddr_in() |
||||
|
zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size) |
||||
|
zeroAddress.sin_family = sa_family_t(AF_INET) |
||||
|
let ref = withUnsafePointer(to: &zeroAddress) { |
||||
|
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) { |
||||
|
SCNetworkReachabilityCreateWithAddress(nil, $0) |
||||
|
} |
||||
|
} |
||||
|
guard let reachRef = ref else { return false } |
||||
|
var flags = SCNetworkReachabilityFlags() |
||||
|
if !SCNetworkReachabilityGetFlags(reachRef, &flags) { return false } |
||||
|
return Self.isReachable(flags: flags) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// 释放资源 |
||||
|
public func dispose() { |
||||
|
if #available(iOS 12.0, *) { |
||||
|
monitor?.cancel() |
||||
|
monitor = nil |
||||
|
} else { |
||||
|
if let reachability = reachability { |
||||
|
SCNetworkReachabilitySetDispatchQueue(reachability, nil) |
||||
|
SCNetworkReachabilitySetCallback(reachability, nil, nil) |
||||
|
} |
||||
|
reachability = nil |
||||
|
reachabilityQueue = nil |
||||
|
} |
||||
|
networkStateListener = nil |
||||
|
} |
||||
|
|
||||
|
/// 判断是否为网络相关错误(关键字简单匹配) |
||||
|
public func isNetworkRelatedError(reason: String, errorDetails: String) -> Bool { |
||||
|
let networkErrorKeywords = [ |
||||
|
"network","connection","timeout","unreachable", |
||||
|
"dns","socket","ssl","tls","certificate", |
||||
|
"网络","连接","超时","不可达" |
||||
|
] |
||||
|
let combined = (reason + " " + errorDetails).lowercased() |
||||
|
return networkErrorKeywords.contains { combined.contains($0.lowercased()) } |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// MARK: - Private |
||||
|
private extension NetworkStateMonitor { |
||||
|
func initNetworkMonitoring() { |
||||
|
if #available(iOS 12.0, *) { |
||||
|
let monitor = NWPathMonitor() |
||||
|
self.monitor = monitor |
||||
|
monitor.pathUpdateHandler = { [weak self] path in |
||||
|
guard let self = self else { return } |
||||
|
let hasInternet = (path.status == .satisfied) |
||||
|
// 只有状态变化时才回调 |
||||
|
if hasInternet && !self.isNetworkAvailable { |
||||
|
self.isNetworkAvailable = true |
||||
|
self.networkStateListener?.onNetworkAvailable() |
||||
|
Self.log(self.tag, "网络连接可用") |
||||
|
} else if !hasInternet && self.isNetworkAvailable { |
||||
|
self.isNetworkAvailable = false |
||||
|
self.networkStateListener?.onNetworkLost() |
||||
|
Self.log(self.tag, "网络连接丢失") |
||||
|
} else { |
||||
|
Self.log(self.tag, "网络能力变化,是否有网络: \(hasInternet)") |
||||
|
} |
||||
|
} |
||||
|
monitor.start(queue: monitorQueue) |
||||
|
} else { |
||||
|
// iOS 10–11 使用 Reachability 回退 |
||||
|
var zeroAddress = sockaddr_in() |
||||
|
zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size) |
||||
|
zeroAddress.sin_family = sa_family_t(AF_INET) |
||||
|
reachability = withUnsafePointer(to: &zeroAddress) { |
||||
|
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) { |
||||
|
SCNetworkReachabilityCreateWithAddress(nil, $0) |
||||
|
} |
||||
|
} |
||||
|
guard let reachability = reachability else { |
||||
|
Self.log(tag, "初始化网络监听失败: Reachability 创建失败") |
||||
|
return |
||||
|
} |
||||
|
let queue = DispatchQueue(label: "NetworkStateMonitor.reachability") |
||||
|
reachabilityQueue = queue |
||||
|
|
||||
|
var context = SCNetworkReachabilityContext( |
||||
|
version: 0, |
||||
|
info: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()), |
||||
|
retain: nil, |
||||
|
release: nil, |
||||
|
copyDescription: nil |
||||
|
) |
||||
|
let callback: SCNetworkReachabilityCallBack = { (_, flags, info) in |
||||
|
guard let info = info else { return } |
||||
|
let monitor = Unmanaged<NetworkStateMonitor>.fromOpaque(info).takeUnretainedValue() |
||||
|
let hasInternet = NetworkStateMonitor.isReachable(flags: flags) |
||||
|
if hasInternet && !monitor.isNetworkAvailable { |
||||
|
monitor.isNetworkAvailable = true |
||||
|
monitor.networkStateListener?.onNetworkAvailable() |
||||
|
NetworkStateMonitor.log(monitor.tag, "网络连接可用") |
||||
|
} else if !hasInternet && monitor.isNetworkAvailable { |
||||
|
monitor.isNetworkAvailable = false |
||||
|
monitor.networkStateListener?.onNetworkLost() |
||||
|
NetworkStateMonitor.log(monitor.tag, "网络连接丢失") |
||||
|
} else { |
||||
|
NetworkStateMonitor.log(monitor.tag, "网络能力变化,是否有网络: \(hasInternet)") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if SCNetworkReachabilitySetCallback(reachability, callback, &context), |
||||
|
SCNetworkReachabilitySetDispatchQueue(reachability, queue) { |
||||
|
// 触发一次初始查询 |
||||
|
var flags = SCNetworkReachabilityFlags() |
||||
|
if SCNetworkReachabilityGetFlags(reachability, &flags) { |
||||
|
self.isNetworkAvailable = Self.isReachable(flags: flags) |
||||
|
} |
||||
|
} else { |
||||
|
Self.log(tag, "初始化网络监听失败: 设置回调/队列失败") |
||||
|
SCNetworkReachabilitySetCallback(reachability, nil, nil) |
||||
|
SCNetworkReachabilitySetDispatchQueue(reachability, nil) |
||||
|
self.reachability = nil |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static func isReachable(flags: SCNetworkReachabilityFlags) -> Bool { |
||||
|
// 经典 Reachability 判定 |
||||
|
let reachable = flags.contains(.reachable) |
||||
|
let requiresConnection = flags.contains(.connectionRequired) |
||||
|
let canConnectAutomatically = flags.contains(.connectionOnTraffic) || flags.contains(.connectionOnDemand) |
||||
|
let canConnectWithoutUser = canConnectAutomatically && !flags.contains(.interventionRequired) |
||||
|
return reachable && (!requiresConnection || canConnectWithoutUser) |
||||
|
} |
||||
|
|
||||
|
static func log(_ tag: String, _ message: String) { |
||||
|
#if DEBUG |
||||
|
print("[\(tag)] \(message)") |
||||
|
#endif |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue