Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager)
2017-08-14 09:23
555 查看
Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager)
本篇主要讲解iOS开发中的网络监控前言
在开发中,有时候我们需要获取这些信息:手机是否联网
当前网络是WiFi还是蜂窝
那么我总结一下具体的使用场景有哪些?肯定有遗漏:
聊天列表,需要实时监控当前的网络是不是可达的,如果不可达,则出现不能联网的提示
在线视屏播放,需要判断当前的网络状态,如果不是WiFi,应该给出流量播放的提示
对于比较重要的网络请求,在请求出错的情况下,判断网路状态,找出请求失败原因。
可以把请求进行缓存后,当监听到网络连接成功后发送。举个例子,每次进app都要把位置信息发给服务器,如果发送失败后,发现是网络不可达造成的失败,那么可以把这个请求放入到一个队列中,在网络可达的时候,开启队列任务。
当网络状态变化时,实时的给用户提示信息
获取某个节点或地址是不是可达的
但是,极其不建议在发请求前,先检测当前的网络是不是可达。因为手机的网络状态是经常变化的》
SCNetworkReachabilityFlags
SCNetworkReachabilityFlags是获取网络状态最核心的东西。我们来看看它有哪些内容:作用
SCNetworkReachabilityFlags能够判断某个指定的网络节点名称或者地址是不是可达的,也能判断该节点或地址是不是需要先建立连接,也可以判断是不是需要用户手动去建立连接。注意:这里所说的连接分为用编程手段连接和用手动建立连接两种
我们只列举出跟本类相关的一些选项:
kSCNetworkReachabilityFlagsReachable表明当前指定的节点或地址是可达的。注意:可达不是代表节点或地址接受到了数据,而是代表数据能够离开本地,因此。就算是可达的,也不一定能够发送成功
kSCNetworkReachabilityFlagsConnectionRequired表明要想和指定的节点或地址通信,需要先建立连接。比如说拨号上网。注意:对于手机来说,如果没有返回该标记,就说明手机正在使用蜂窝网路或者WiFi
kSCNetworkReachabilityFlagsConnectionOnTraffic表明要想和指定的节点或地址通信,必须先建立连接,但是在当前的网络配置下,目标是可达的。注意:任何连接到指定的节点或地址的请求都会触发该标记,举个例子,在很多地方需要输入手机,获取验证码后才能联网,就是这个原理
kSCNetworkReachabilityFlagsConnectionOnDemand表明要想和指定的节点或地址通信,必须先建立连接,但是在当前的网络配置下,目标是可达的。但是建立连接必须通过
CFSocketStream APIs才行,其他的APIs不能建立连接
kSCNetworkReachabilityFlagsInterventionRequired表明要想和指定的节点或地址通信,必须先建立连接,但是在当前的网络配置下,目标是可达的。需要用户手动提供一些数据,比如密码或者token
kSCNetworkReachabilityFlagsIsWWAN表明是不是通过蜂窝网络连接
上边的这些选项,会在下边的一个核心方法中使用到,我们在下边的代码中在给出说明。
ConnectionType
/// Defines the various connection types detected by reachability flags. /// /// - ethernetOrWiFi: The connection type is either over Ethernet or WiFi. /// - wwan: The connection type is a WWAN connection. public enum ConnectionType { case ethernetOrWiFi case wwan }
对于手机而言,我们需要的连接类型就两种,一种是蜂窝网络,另一种是WiFi网络。因此在设计NetworkReachabilityManager的时候,通过上边的枚举获取当前的网络连接类型。
NetworkReachabilityStatus
swift /// Defines the various states of network reachability. /// /// - unknown: It is unknown whether the network is reachable. /// - notReachable: The network is not reachable. /// - reachable: The network is reachable. public enum NetworkReachabilityStatus { case unknown case notReachable case reachable(ConnectionType) }
网络状态明显要比网络类型范围更大,因此又增加了两个选项,一个表示当前的网络是未知的,另一个表示当前的网路不可达。
综上所述,我们的目的就是拿到这个NetworkReachabilityStatus,那么NetworkReachabilityManager是如何把NetworkReachabilityStatus传递出来的呢? 答案就是闭包,
/// A closure executed when the network reachability status changes. The closure takes a single argument: the /// network reachability status. public typealias Listener = (NetworkReachabilityStatus) -> Void
swift的闭包,我们已经很熟悉了,在开发中,首先初始化NetworkReachabilityManager,然后设置Listener,第三部开启监控,这个开启监控的方法会在下边讲到。
Properties
在NetworkReachabilityManager中,属性分为public和private,我们先看public部分:/// Whether the network is currently reachable. public var isReachable: Bool { return isReachableOnWWAN || isReachableOnEthernetOrWiFi } /// Whether the network is currently reachable over the WWAN interface. public var isReachableOnWWAN: Bool { return networkReachabilityStatus == .reachable(.wwan) } /// Whether the network is currently reachable over Ethernet or WiFi interface. public var isReachableOnEthernetOrWiFi: Bool { return networkReachabilityStatus == .reachable(.ethernetOrWiFi) } /// The current network reachability status. public var networkReachabilityStatus: NetworkReachabilityStatus { guard let flags = self.flags else { return .unknown } return networkReachabilityStatusForFlags(flags) } /// The dispatch queue to execute the `listener` closure on. public var listenerQueue: DispatchQueue = DispatchQueue.main /// A closure executed when the network reachability status changes. public var listener: Listener?
public表明我们可以通过NetworkReachabilityManager实例直接获得的属性,能够让我们很方便的获取我们想要的数据。我们对这些属性做一些简单的说明:
isReachable: Bool当前网络是可达的,要么是蜂窝网络,要么是WiFi连接
isReachableOnWWAN: Bool表明当前网络是通过蜂窝网络连接
isReachableOnEthernetOrWiFi: Bool表明当前网络是通过WiFi连接
networkReachabilityStatus: NetworkReachabilityStatus返回当前的网络状态,这也是上边3个判断的基础
listenerQueue监听listener在那个队列中调用,默认的是主队列
listener: Listener监听闭包,当网络状态发生变化时会调用
上边这些public属性有的是只读的,有的不是,我们在看看private属性:
flags: SCNetworkReachabilityFlags?主要目的是获取flags,在上边我们介绍过,网络状态就是根据flags判断出来的是通过下边的方法获取到的:
@available(iOS 2.0, *) public func SCNetworkReachabilityGetFlags(_ target: SCNetworkReachability, _ flags: UnsafeMutablePointer<SCNetworkReachabilityFlags>) -> Bool
reachability: SCNetworkReachability必不可少的对象,有了它才能获取flags
previousFlags: SCNetworkReachabilityFlags用于记录当前的flags,在收到系统的callBack方法后,通过比较现在的flags和previousFlags来判断是不是要调用listener函数
Initialization
关于初始化,NetworkReachabilityManager提供了三种选择:通过指定host
/// Creates a `NetworkReachabilityManager` instance with the specified host. /// /// - parameter host: The host used to evaluate network reachability. /// /// - returns: The new `NetworkReachabilityManager` instance. public convenience init?(host: String) { guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil } self.init(reachability: reachability) }
通过init方法会默认的设置为指向0.0.0.0
/// Creates a `NetworkReachabilityManager` instance that monitors the address 0.0.0.0. /// /// Reachability treats the 0.0.0.0 address as a special token that causes it to monitor the general routing /// status of the device, both IPv4 and IPv6. /// /// - returns: The new `NetworkReachabilityManager` instance. public convenience init?() { var address = sockaddr_in() address.sin_len = UInt8(MemoryLayout<sockaddr_in>.size) address.sin_family = sa_family_t(AF_INET) guard let reachability = withUnsafePointer(to: &address, { pointer in return pointer.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout<sockaddr>.size) { return SCNetworkReachabilityCreateWithAddress(nil, $0) } }) else { return nil } self.init(reachability: reachability) }
通过指定SCNetworkReachability
private init(reachability: SCNetworkReachability) { self.reachability = reachability self.previousFlags = SCNetworkReachabilityFlags() }
deinit
deinit { stopListening() }
上边的代码表明,在NetworkReachabilityManager被销毁的时候,会停止监控,因此在开发中就要额外注意这一点,最好让控制器强引用它。
startListening
在开发中,对于开发某个功能,我有时候会称为开发某种能力类,我们可以采取自上而下的方法,我先定义出最基本的伪代码,对于网络监控我们的伪代码就应该是下边这样的:创建一个监控者
设置监控回调事件
开始监控
停止监控
在这里讲点额外的编程技巧,上边的4个伪代码我们可以成为子程序,每个子程序都应该有一定的内聚性要求,就是说每个子程序最好能够实现一个单一的功能。子程序会出现成对出现的情况,比如开始和停止,等等。那么我们现在要讲的就是第三步,开始监控。
@discardableResult public func startListening() -> Bool { var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) context.info = Unmanaged.passUnretained(self).toOpaque() let callbackEnabled = SCNetworkReachabilitySetCallback( reachability, { (_, flags, info) in let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!).takeUnretainedValue() reachability.notifyListener(flags) }, &context ) let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue) listenerQueue.async { self.previousFlags = SCNetworkReachabilityFlags() self.notifyListener(self.flags ?? SCNetworkReachabilityFlags()) } return callbackEnabled && queueEnabled }
@discardableResult表明可以忽略返回值。其实开始监控网络状态就分为两部:
设置Callback回调函数
设置Callback回调队列
当然必要的前提是必须初始化了一个reachability。
这里有一些很有意思的东西,可能我们在swift中是不常见的。比如:
Unmanaged.passUnretained(self).toOpaque(),比如:
let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!).takeUnretainedValue()
/// A type for propagating an unmanaged object reference. /// /// When you use this type, you become partially responsible for /// keeping the object alive. public struct Unmanaged<Instance : AnyObject> { /// Unsafely turns an opaque C pointer into an unmanaged class reference. /// /// This operation does not change reference counts. /// /// let str: CFString = Unmanaged.fromOpaque(ptr).takeUnretainedValue() /// /// - Parameter value: An opaque C pointer. /// - Returns: An unmanaged class reference to `value`. public static func fromOpaque(_ value: UnsafeRawPointer) -> Unmanaged<Instance> /// Unsafely converts an unmanaged class reference to a pointer. /// /// This operation does not change reference counts. /// /// let str0: CFString = "boxcar" /// let bits = Unmanaged.passUnretained(str0) /// let ptr = bits.toOpaque() /// /// - Returns: An opaque pointer to the value of this unmanaged reference. public func toOpaque() -> UnsafeMutableRawPointer /// Creates an unmanaged reference with an unbalanced retain. /// /// The instance passed as `value` will leak if nothing eventually balances /// the retain. /// /// This is useful when passing an object to an API which Swift does not know /// the ownership rules for, but you know that the API expects you to pass /// the object at +1. /// /// - Parameter value: A class instance. /// - Returns: An unmanaged reference to the object passed as `value`. public static func passRetained(_ value: Instance) -> Unmanaged<Instance> /// Creates an unmanaged reference without performing an unbalanced /// retain. /// /// This is useful when passing a reference to an API which Swift /// does not know the ownership rules for, but you know that the /// API expects you to pass the object at +0. /// /// CFArraySetValueAtIndex(.passUnretained(array), i, /// .passUnretained(object)) /// /// - Parameter value: A class instance. /// - Returns: An unmanaged reference to the object passed as `value`. public static func passUnretained(_ value: Instance) -> Unmanaged<Instance> /// Gets the value of this unmanaged reference as a managed /// reference without consuming an unbalanced retain of it. /// /// This is useful when a function returns an unmanaged reference /// and you know that you're not responsible for releasing the result. /// /// - Returns: The object referenced by this `Unmanaged` instance. public func takeUnretainedValue() -> Instance /// Gets the value of this unmanaged reference as a managed /// reference and consumes an unbalanced retain of it. /// /// This is useful when a function returns an unmanaged reference /// and you know that you're responsible for releasing the result. /// /// - Returns: The object referenced by this `Unmanaged` instance. public func takeRetainedValue() -> Instance /// Performs an unbalanced retain of the object. public func retain() -> Unmanaged<Instance> /// Performs an unbalanced release of the object. public func release() /// Performs an unbalanced autorelease of the object. public func autorelease() -> Unmanaged<Instance> }
这里提供一个文章地址[HandyJSON] 设计思路简析,关于swift中指针的使用可以参考这篇文章。很强大啊。后续我会写
HandyJson的源码解读文章。
在上边的开始监控中有一个函数:
notifyListener,这个函数的目的就是通知监听者,也就是触发回调函数。
func notifyListener(_ flags: SCNetworkReachabilityFlags) { guard previousFlags != flags else { return } previousFlags = flags listener?(networkReachabilityStatusForFlags(flags)) }
networkReachabilityStatusForFlags
这个函数是根据flags获取状态的核心函数,但是我觉得没什么好说的,在开发中用的也不多,我们把代码粘一下,然后重点来说说swift中运算符==重载:
func networkReachabilityStatusForFlags(_ flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus { /// 这里的contains函数要传递的值是OptionSet自身,因此.reachable换成SCNetworkReachabilityFlags.reachable也是可以的,reachable是一个静态方法 /// flags.contains(.reachable)如果是true,就代表有网络连接 guard flags.contains(.reachable) else { return .notReachable } var networkStatus: NetworkReachabilityStatus = .notReachable if !flags.contains(.connectionRequired) { networkStatus = .reachable(.ethernetOrWiFi) } if flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic) { if !flags.contains(.interventionRequired) { networkStatus = .reachable(.ethernetOrWiFi) } } #if os(iOS) if flags.contains(.isWWAN) { networkStatus = .reachable(.wwan) } #endif return networkStatus }
运算符重载
要想重载==,需要实现
Equatable协议:
public protocol Equatable { /// Returns a Boolean value indicating whether two values are equal. /// /// Equality is the inverse of inequality. For any values `a` and `b`, /// `a == b` implies that `a != b` is `false`. /// /// - Parameters: /// - lhs: A value to compare. /// - rhs: Another value to compare. public static func ==(lhs: Self, rhs: Self) -> Bool }
其实,这种思想还是很重要的,在开发中可以通过这种方式来判断两个模型是不是相同,等等很多种使用场景。我简单的把Apple文档中的注释说明部分翻译一下。
==和
!=是对立统一的关系,我们自定义了
==,同理,
!=也就支持了。在swift中,很多基本的数据类型都支持了
Equatable协议。
Equatable协议的一个典型的应用场景就是判断一个集合中是否包含某个值。在swift中,如果集合中的值都实现了
Equatable协议,那么就可以通过
contains(_:)方法来判断是不是包含该值。这也说明了
contains(_:)内部实现应该是通过
==来实现的。使用
contains(_:)方法的好处就是省去了我们遍历数据,然后再进行判断的繁琐步骤。我们看个例子:
/// let students = ["Nora", "Fern", "Ryan", "Rainer"] /// /// let nameToCheck = "Ryan" /// if students.contains(nameToCheck) { /// print("\(nameToCheck) is signed up!") /// } else { /// print("No record of \(nameToCheck).") /// } /// // Prints "Ryan is signed up!"
需要把
==声明成为自定义类型的静态方法
假如说我们有一个街道地址的结构体:
/// struct StreetAddress { /// let number: String /// let street: String /// let unit: String? /// /// init(_ number: String, _ street: String, unit: String? = nil) { /// self.number = number /// self.street = street /// self.unit = unit /// } /// }
我们让
StreetAddress实现
Equatable协议:
/// /// extension StreetAddress: Equatable { /// static func == (lhs: StreetAddress, rhs: StreetAddress) -> Bool { /// return /// lhs.number == rhs.number && /// lhs.street == rhs.street && /// lhs.unit == rhs.unit /// } /// } ///
接下来我们就能使用系统的
contains(_:)方法来判断一个集合中是不是包含摸个街道地址了。
/// /// let addresses = [StreetAddress("1490", "Grove Street"), /// StreetAddress("2119", "Maple Avenue"), /// StreetAddress("1400", "16th Street")] /// let home = StreetAddress("1400", "16th Street") /// /// print(addresses[0] == home) /// // Prints "false" /// print(addresses.contains(home)) /// // Prints "true" ///
有了上边的知识,我们在看看
NetworkReachabilityManager是怎么用的:
extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {} /// Returns whether the two network reachability status values are equal. /// /// - parameter lhs: The left-hand side value to compare. /// - parameter rhs: The right-hand side value to compare. /// /// - returns: `true` if the two values are equal, `false` otherwise. public func ==( lhs: NetworkReachabilityManager.NetworkReachabilityStatus, rhs: NetworkReachabilityManager.NetworkReachabilityStatus) -> Bool { switch (lhs, rhs) { case (.unknown, .unknown): return true case (.notReachable, .notReachable): return true case let (.reachable(lhsConnectionType), .reachable(rhsConnectionType)): return lhsConnectionType == rhsConnectionType default: return false } }
在swift中,static函数还可以像上边这么用,把函数写到类的代码块之外,当然,上边的代码也可以这么写:
extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable { public static func ==( lhs: NetworkReachabilityManager.NetworkReachabilityStatus, rhs: NetworkReachabilityManager.NetworkReachabilityStatus) -> Bool { switch (lhs, rhs) { case (.unknown, .unknown): return true case (.notReachable, .notReachable): return true case let (.reachable(lhsConnectionType), .reachable(rhsConnectionType)): return lhsConnectionType == rhsConnectionType default: return false } } }
总结
由于知识水平有限,如有错误,还望指出链接
Alamofire源码解读系列(一)之概述和使用简书-----博客园
Alamofire源码解读系列(二)之错误处理(AFError)
简书-----博客园
Alamofire源码解读系列(三)之通知处理(Notification)
简书-----博客园
Alamofire源码解读系列(四)之参数编码(ParameterEncoding)
简书-----博客园
Alamofire源码解读系列(五)之结果封装(Result)
简书-----博客园
Alamofire源码解读系列(六)之Task代理(TaskDelegate)
简书-----博客园
相关文章推荐
- Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager)
- Alamofire源码解读系列(一)之概述和使用
- Alamofire源码解读系列(二)之错误处理(AFError)
- Alamofire源码解读系列(三)之通知处理(Notification)
- Alamofire源码解读系列(五)之结果封装(Result)
- Alamofire源码解读系列(九)之响应封装(Response)
- Alamofire源码解读系列(九)之响应封装(Response)
- Alamofire源码解读系列(十二)之时间轴(Timeline)
- Alamofire源码解读系列(十二)之请求(Request)
- Alamofire源码解读系列之错误处理(AFError)
- Alamofire源码解读系列(四)之参数编码(ParameterEncoding)
- AFNetworkReachabilityManager 监控网络状态(四)
- AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager
- AFNetworkReachabilityManager 监控网络状态(四)
- AFNetworkReachabilityManager 监控网络状态(四)
- Alamofire源码解读
- AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager
- alamofire 源码解读
- swift之判断网络状态Alamofire、Reachability
- [置顶] AFNetworking3.0源码解读(一)之 AFNetworkReachabilityManager