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