Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager)

本篇主要讲解iOS开发中的网络监控

前言

在开发中,有时候我们需要获取这些信息:

  • 手机是否联网
  • 当前网络是WiFi还是蜂窝

那么我总结一下具体的使用场景有哪些?肯定有遗漏:

  1. 聊天列表,需要实时监控当前的网络是不是可达的,如果不可达,则出现不能联网的提示
  2. 在线视屏播放,需要判断当前的网络状态,如果不是WiFi,应该给出流量播放的提示
  3. 对于比较重要的网络请求,在请求出错的情况下,判断网路状态,找出请求失败原因。
  4. 可以把请求进行缓存后,当监听到网络连接成功后发送。举个例子,每次进app都要把位置信息发给服务器,如果发送失败后,发现是网络不可达造成的失败,那么可以把这个请求放入到一个队列中,在网络可达的时候,开启队列任务。
  5. 当网络状态变化时,实时的给用户提示信息
  6. 获取某个节点或地址是不是可达的

但是,极其不建议在发请求前,先检测当前的网络是不是可达。因为手机的网络状态是经常变化的》

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 ethernetOrWiFicase wwan}

对于手机而言,我们需要的连接类型就两种,一种是蜂窝网络,另一种是WiFi网络。因此在设计NetworkReachabilityManager的时候,通过上边的枚举获取当前的网络连接类型。

NetworkReachabilityStatus

 /// 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 unknowncase notReachablecase 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 inreturn 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 = reachabilityself.previousFlags = SCNetworkReachabilityFlags()}

deinit

 deinit {stopListening()}

上边的代码表明,在NetworkReachabilityManager被销毁的时候,会停止监控,因此在开发中就要额外注意这一点,最好让控制器强引用它。

startListening

在开发中,对于开发某个功能,我有时候会称为开发某种能力类,我们可以采取自上而下的方法,我先定义出最基本的伪代码,对于网络监控我们的伪代码就应该是下边这样的:

  1. 创建一个监控者
  2. 设置监控回调事件
  3. 开始监控
  4. 停止监控

在这里讲点额外的编程技巧,上边的4个伪代码我们可以成为子程序,每个子程序都应该有一定的内聚性要求,就是说每个子程序最好能够实现一个单一的功能。子程序会出现成对出现的情况,比如开始和停止,等等。那么我们现在要讲的就是第三步,开始监控。

    @discardableResultpublic 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) inlet 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表明可以忽略返回值。其实开始监控网络状态就分为两部:

  1. 设置Callback回调函数
  2. 设置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 = flagslistener?(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 = .notReachableif !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) }#endifreturn 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 truecase (.notReachable, .notReachable):return truecase let (.reachable(lhsConnectionType), .reachable(rhsConnectionType)):return lhsConnectionType == rhsConnectionTypedefault: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 truecase (.notReachable, .notReachable):return truecase let (.reachable(lhsConnectionType), .reachable(rhsConnectionType)):return lhsConnectionType == rhsConnectionTypedefault:return false}}
}

总结

由于知识水平有限,如有错误,还望指出

链接

Alamofire源码解读系列(一)之概述和使用 简书-----博客园

Alamofire源码解读系列(二)之错误处理(AFError) 简书-----博客园

Alamofire源码解读系列(三)之通知处理(Notification) 简书-----博客园

Alamofire源码解读系列(四)之参数编码(ParameterEncoding) 简书-----博客园

Alamofire源码解读系列(五)之结果封装(Result) 简书-----博客园

Alamofire源码解读系列(六)之Task代理(TaskDelegate) 简书-----博客园

Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager)相关推荐

  1. Alamofire源码解读系列(九)之响应封装(Response)

    本篇主要带来Alamofire中Response的解读 前言 在每篇文章的前言部分,我都会把我认为的本篇最重要的内容提前讲一下.我更想同大家分享这些顶级框架在设计和编码层次究竟有哪些过人的地方?当然, ...

  2. Alamofire源码解读系列(十二)之请求(Request)

    本篇是Alamofire中的请求抽象层的讲解 前言 在Alamofire中,围绕着Request,设计了很多额外的特性,这也恰恰表明,Request是所有请求的基础部分和发起点.这无疑给我们一个Req ...

  3. Alamofire源码解读系列(十一)之多表单(MultipartFormData)

    本篇讲解跟上传数据相关的多表单 前言 我相信应该有不少的开发者不明白多表单是怎么一回事,然而事实上,多表单确实很简单.试想一下,如果有多个不同类型的文件(png/txt/mp3/pdf等等)需要上传给 ...

  4. Alamofire源码解读系列(五)之结果封装(Result)

    本篇讲解Result的封装 前言 有时候,我们会根据现实中的事物来对程序中的某个业务关系进行抽象,这句话很难理解.在Alamofire中,使用Response来描述请求后的结果.我们都知道Alamof ...

  5. Alamofire源码解读系列(一)之概述和使用

    尽管Alamofire的github文档已经做了很详细的说明,我还是想重新梳理一遍它的各种用法,以及这些方法的一些设计思想 前言 因为之前写过一个AFNetworking的源码解读,所以就已经比较了解 ...

  6. spring源码解读系列(八):观察者模式--spring监听器详解

    一.前言 在前面的文章spring源码解读系列(七)中,我们继续剖析了spring的核心refresh()方法中的registerBeanPostProcessors(beanFactory)(完成B ...

  7. 【注意力机制集锦】Channel Attention通道注意力网络结构、源码解读系列一

    Channel Attention网络结构.源码解读系列一 SE-Net.SK-Net与CBAM 1 SENet 原文链接:SENet原文 源码链接:SENet源码 Squeeze-and-Excit ...

  8. py-faster-rcnn源码解读系列

    转载自: py-faster-rcnn源码解读系列(一)--train_faster_rcnn_alt_opt.py - sunyiyou9的博客 - 博客频道 - CSDN.NET http://b ...

  9. Hadoop源码解读系列目录

    Hadoop源码解读系列 1.hadoop源码|common模块-configuration详解 2.hadoop源码|core模块-序列化与压缩详解 3.hadoop源码|core模块-远程调用与N ...

最新文章

  1. jsp ul设置滚动条_jquery实现Li滚动时滚动条自动添加样式的方法
  2. Android开源项目分类汇总-转载
  3. ElasticSearch大批量数据入库
  4. release和retain还有多少人在用
  5. 剑指offer--不用加减乘除做加法
  6. MySQL8.0.26 开启bin_log日志 linux
  7. java方法重载_在Python中该如何实现Java的重写与重载
  8. 适应关键业务环境的加湿系统
  9. Unity3D实践1.1:解决摄像机跟随中的视野遮挡问题
  10. CPU acceleration status:HAXM must be updated(version 1.1.16.0.1)
  11. 小D课堂-SpringBoot 2.x微信支付在线教育网站项目实战_3-4.动态Sql语句Mybaties SqlProvider...
  12. java秒数格式转换_Java中整数(秒数)转换为时分秒格式(xx:xx:xx)
  13. 集线器、交换机和网桥三者有什么区别
  14. 形式化方法课程学习笔记(一)|Cop的安装以及简单使用
  15. win7计算机系统还原,使用Win7系统自带的系统还原功能将电脑恢复到正常状态
  16. MCE | 单胺能 非单胺能对抑郁症的作用
  17. 鸿蒙 OS 的到来,能为我们改变什么?
  18. [2016 T-EDGE]王坚对话无人机教父:创新就要享受走在悬崖边的刺激
  19. SpringBoot Data整合ElasticSearch
  20. 无痕刷新token-无需提供刷新token接口方式

热门文章

  1. java三色球问题_2020100期专业玩彩双色球走势分析
  2. 小腹右侧突然疼了一下_腰椎间盘膨出,为什么不是脊椎柱中间疼,而是左侧疼?...
  3. JavaEE系统架构师学习路线
  4. 上下文保存 中断_从操作系统(Windows)的角度讨论中断和异常机制
  5. 4核a5中断linux,Cortex A5 MPcore寄存器TPIDRPRW复位值不为零,造成Linux Kernel不能启动的问题...
  6. java向hdfs提交命令_Java语言操作HDFS常用命令测试代码
  7. linux mysql查看所有表_Linux之系统操作命令
  8. 计算机考研 东华大学,东华大学(专业学位)计算机技术考研难吗
  9. php func_get_args(),PHP中func_get_args(),func_get_arg(),func_num_args()有什么不同
  10. Connected to the target VM, address: '127.0.0.1:60885', transport: 'socket'