AFNetworking <一> AFNetwork
AFNetworkReachabilityManager 是用来实时监听网络状态的。具体来说,是监听手机(主机)是否能够向外发出数据包。注意,AFNetworkReachabilityManager 只是监听手机(主机)能不能把数据包发出去,而不管目标主机是否接受。简而言之,就是确认箭能不能射出去,但是不保证射的中。
AFNetworkReachabilityManager 在一般情况下的调用是 [[AFNetworkReachabilityManager sharedManager]startMonitoring]。由于对网络状态的监听,贯彻整个app的运行,所以,一般情况下应用开发中开发者不会手动调用 stopMonitoring 方法。
这里需要注意的是,AFNetworkReachabilityManager 提供了多个获取其实例的方法。sharedManager是获取单例,而 manager 是获取一个新初始化的实例,不是单例。还有几个别的获取实例的方法如下:
- (instancetype)managerForDomain:(NSString *)domain; —— 根据域名来获取一个 ReachabilityManager 的实例;
-
(instancetype)managerForAddress:(const void *)address; —— 根据 socket 地址(sockaddr_in6,ipv6) 来获取一个 ReachabilityManager 的实例。
一般程序中,是用的都是 sharedManager,而其他获取实例的方法,可以让开发者根据实际业务需求,对有需要的网络进行监听。
sharedManager中实际上是使用 dispatch_once 创建一个单例。
sharedManager
再来看下 manager 这个方法:
manager
可以看到,这个方法里面,先是创建了一个 sockaddr 结构体。这里,做了下版本控制,分别对应 ipv6 和 ipv4两种 sockaddr 结构体。然后调用 managerForAddress 方法:
managerForAddress
managerForAddress中,调用 SCNetwork中的方法,创建一个用于监听网络状态的网络状态句柄(SCNetworkReachabilityRef)。这里,SC 是 system configuration 的缩写,这是一个 Apple 底层的库。
最后看下 initWithReachability 这个初始化方法:
initWithReachability
忙了这么一大圈,其实就是为了得到一个 SCNetworkReachabilityRef,他在监听网络状态时很关键。SCNetworkReachabilityRef是一个结构体,我们来看看 Apple 的代码注释:
SCNetworkReachabilityRef
文档中说,SCNetworkReachabilityRef 的 api 使得一个应用可以获知系统当前的网络配置状态,以及判断网络是否能够连接目标主机。此外,当网络状态发生改变时,可以通过通知监听网络是否能够连接。“Reachability”反映出,一个应用向网络堆栈发出的数据包,是否能够从本机发出。需要注意的是,“Reachability”只是表征数据包能够发出,并不保证数据能够切实的被主机收到。
可以看到,SCNetworkReachabilityRef 其实是一个网络地址或者说名称的句柄,Apple把所有相关的一些列信息都用着一个句柄在操作。同时,在Apple的代码注释中可以看到,要让 SCNetworkReachabilityRef 正常工作的话,需要将 SCNetworkReachabilityRef 加入到 运行循环 或者是 调度队列中。
简单来说,所谓的网络监听,就是观察 SCNetworkReachabilityRef 中的参数状态,所有的一切其实都是围绕着他的。在 SCNetworkReachabilityRef 中有 target host ,整个监听的过程,其实就是监听能不能对这个目标主机发出数据包。
简单的看下 startMonitoring 方法。startMonitoring 中,主要是是对 SCNetwork 这一Apple底层的网络框架的使用。SCNetwork 存在于 systemConfiguration库中,顾名思义是对系统的配置。AFNetworking 中的 startMonitoring 方法实现:
startMonitoring
让我们一步一步来看。方法一开始会将上次的监听任务停止。随后,判断 self 的 networkReachability 是否为空。然后创建了一个闭包(block)这个 block 是使用于 SCNetworkReachabilityRef 的回调中的。使用 __weak 目的是为了不让 self 的引用计数 +1,使用 __strong 是为了让实例在 block 运行完之前不被释放。当然,如果你使用的是 shareManager 的话,那么其实这里的 __weak 和 __strong 都没有存在的必要,因为 shareManager 是一个单例。
block显然是在网络状态发生变化时被调用的,很简单,不多说了。之后是创建了一个 SCNetworkReachabilityContext 的结构体。来看下 SCNetworkReachabilityContext 的定义:
SCNetworkReachabilityContext
SCNetworkReachabilityContext 包含了用户指定的,相对于 SCNetworkReachability 的数据和回调函数。
version —— 版本号,是传递给 SCDynamicStore (系统配置动态存储库)来创建函数用的。这个结构体的版本号是 0
info —— 一个 C 指针,指向一个用户指定的数据块。(也就是前一步中创建的那个block)
retain —— 用在回调 info 的时候,为其引用加1
release —— 用来对之前引用 +1 的 info,引用减1
retain 和 release 这里主要注意的是函数定义要正确,不然会有不可知的结果。这里有个疑问就是这两个 retain 和 release 究竟是干什么使的?先往下看。
copyDescription —— 回调时,为 info 提供描述。这个直接传 NULL 就行,基本没什么用。
然后,是 SCNetworkReachabilitySetCallback :
SCNetworkReachabilitySetCallback
就是为 SCNetworkReachability添加回调函数,target 就是之前设定的 网络句柄。简单点说,他就是一个目标,监听本机能不能像他发出数据包(仅仅是监听能不能发出,不保证是不是接受)。这个方法中还要穿入一个回调函数,和一个 SCNetworkReachabilityContext 。回调函数用来当网络状态发生变化时,让系统进行回调。最后一个 context 是和 回调函数相关联的 SCNetworkReachabilityContext (网络可连接上下文)。这儿说的有点绕,但继续往下看。
这里要看下 SCNetworkReachabilityCallBack :
SCNetworkReachabilityCallBack
回调函数需要包含三个参数,一个是监听的目标,一个是flag表示新的网络状态,还有一个是一个C指针,指向用户设定的闭包(也就是 SCNetworkReachabilityContext 中的 info)
到了这里应该明白,其实 SCNetworkReachabilityContext 保存的是回调函数以及相关的一些数据操作,SCNetworkReachabilityCallBack是回调函数,其中的 info 来自于 SCNetworkReachabilityContext。
我觉得 SCNetworkReachabilityContext 让开发者能在设定回调函数时,有更多的自由,在 retain 和 release 的时候可以做更多的事儿。(可能有更多的作用,但是我暂时没有看出来,请知道的朋友不吝赐教)
到了这里,应该对整个回调的结构有一些认识了。简而言之,先创建一个要监听的目标,然后把回调函数用 SCNetworkReachabilityContext 结构体包裹。然后,为这个监听目标设置回调方法。当然,看到这里,你可能会觉得为什么这么麻烦?直接把前面创建的 callback作为回调不就得了?
callback
这里有几点,第一 SCNetworkReachabilitySetCallback 中,对回调函数的参数类型是有要求的,需要回调函数接收三个参数 —— target,callout,info。而 callback 只接受一个改变后的网络状态作为参数。二者实际上是不同的函数,直接传,或者强转的话会出错。事实上,AFN在这里set的回调函数 —— AFNetworkReachabilityCallback 仍然不是不是最终的真正的回调所在。看一下 AFNetworkReachabilityCallback:
AFNetworkReachabilityCallback
处理网络状态变化的入口有两个,一个是设置回调 block ,另一个是 接受通知。AFPostReachabilityStatusChange 就是调用一次用户设置的 回调 block ,然后发一个通知。
可以看到,AFNetworkReachabilityCallback 实际上还要调用 AFPostReachabilityStatusChange 。而在 AFPostReachabilityStatusChange 中并没有用到 target 这个参数。这么写,看上去是比较繁琐的,但是有一个巨大的好处 —— 一个方法对应一个功能。
对于 AFNetworkReachabilityCallback 和 AFPostReachabilityStatusChange 确实只要对 AFPostReachabilityStatusChange 做一下修改,在其参数列表中,添加一个 target 然后这个 target 参数不去使用,就直接可以将其作为回调函数传给 SCNetworkReachabilitySetCallback 了。这样做在功能上是没有问题的,但是如此一来这么设计 函数的 api 是不优雅的 —— 函数中多了一个不需要的参数。而且,AFPostReachabilityStatusChange 本身是可以直接调用的,而不仅仅是作为 SCNetworkReachabilitySetCallback 的参数。所以,AFN 的作者在这里使用了一个独立的 AFNetworkReachabilityCallback 作为一个二传手,最终调用 AFPostReachabilityStatusChange。这也是给我们在日常编程时的一个启发 —— 尽量做到一个函数就是一个功能;设计函数 api 时不要出现多余的入参。
在设置完了回调函数后,将 networkReachability 加入到 runloop中,达到实时�监听的目的 —— SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
这个 SCNetworkReachabilityScheduleWithRunLoop 方法比较简单,就是将 networkReachability 添加到 某个运行循环上(这里是主循环),然后设定一个模式,在这个模式下都会触发监听。
这里选的是 kCFRunLoopCommonModes 这样当用户操作UI的时候,�也会继续监听。
最后,直接在最低优先级的的队列上,调用一次 AFPostReachabilityStatusChange 函数,发送目前的网络可连接状态。