AFNetworking 源码阅读之网络监听 Reachability

AFNetWorking源码阅读

AFNetWorking 是一款用于 Cocoa 上的网络库,它适用于 iOS, macOS, watchOS, 以及 tvOS 等各个系统。AFNetWorking 的优点在于,它提供了一套非常全面并且易于使用的 API,让我们在隔绝和 Cocoa 原生网络架构的繁琐交互的过程中,编写与网络相关的代码。

阅读 AFNetWorking 源码不仅能让开发者更好的理解和运用这个人气超高的网络库,还能从中学到许多优秀的开发技巧,感受大神的风采。

AFNetWorking 源码中,主要包含了四大内容:

  • Reachability 网络监听
  • NSURLSession 的封装
  • Security 安全策略
  • Serialization 序列化

本文是对 Reachability 网络监听模块AFNetworkReachabilityManager 的源码阅读做的记录。

一、AFNetworkReachabilityManager 是如何使用的

AFNetworkReachabilityManagerAFNetWorking 的一个子模块,实际上它能够完全脱离 AFNetWorking 来使用。
以下是一段使用 AFNetWorking 的样例:

// 使用 block
  [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
       NSLog(@"block 获取当前网络状态:%d",status);
  }];
// 使用通知中心
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkChangeNotificationWithInfo:) name:AFNetworkingReachabilityDidChangeNotification object:nil];

- (void)networkChangeNotificationWithInfo:(NSNotification)notification {
    NSDictionary *dic = notification.userInfo;
    if(dic) {
        AFNetworkReachabilityStatus status = dic[AFNetworkingReachabilityNotificationStatusItem];
        NSLog(@"通知中心 获取当前网络状态:%d",status);
    }
}

以上分别是使用block通知中心来监听网络变化,记得添加头文件

@import "AFNetworking.h"
或者
@import "AFNetworkReachabilityManager.h"

没有任何难度,这也许就是强大又全面的 API 的一种表征吧。

当然我们主要是要了解它的内部实现的。

二、 AFNetworkReachabilityManager 的实现

AFNetWorking 的网络监听是通过 AFNetworkReachabilityManager 类来实现的。

AFNetworkReachabilityManager.h 文件暴露接口来看,这个类实际上相当的简单。文件中,除了定义了 AFNetworkReachabilityManager 类型之外,还包含一个表示网络状态的枚举和两个的网络通知的常量字段声明,以及一个网络监听的回调函数

1. 网络状态枚举AFNetworkReachabilityStatus

源码中我添加了一些注释:


AFNetworkReachabilityStatus

这四个状态将表示整个网络监听功能的最终结果。

2. 两个网络通知的常量字段和一个网络状态描述的回调函数

image.png

2.1网络变化通知的常量字段

FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;

这两个字段中, AFNetworkingReachabilityDidChangeNotification用于网络状态发生变化时的通知字段,我们在开发中,只要使用通知中心 NSNotificationCenter 监听这个字段,就能在每一次的网络改变时得到监听回调。这个通知中的回调参数是一个字典,字典中保存了网络状态,类似于:

{key:AFNetworkReachabilityStatus}  

这个 key 是一个字符串,也即是上面的 AFNetworkingReachabilityNotificationStatusItem字段。我们得到字典之后,通过key值就可以获取到改变后的网络状态了。

2.2网络状态描述的回调函数
获取网络时,我们获取到的是AFNetworkReachabilityStatus的枚举值,有时候我们需要得到这个状态的描述,那么通过网络状态描述的回调函数即可获取:

FOUNDATION_EXPORT NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status);

它的内部实现这样的:


AFStringFromNetworkReachabilityStatus实现

我们能够通过对这些文字进行本地化处理,获取到的将是具有本地化信息的文字了。

3. AFNetworkReachabilityManager 结构

AFNetworkReachabilityManager 是网络监听的核心类,它是基于框架SystemConfiguration实现网络监听的。

3.1 AFNetworkReachabilityManager 初始化

AFNetworkReachabilityManager 提供了五个初始化方法和两个禁用初始化方法:

+ (instancetype)sharedManager;  // 单例
+ (instancetype)manager; // 自动创建的非单例
+ (instancetype)managerForDomain:(NSString *)domain;  // 使用指定的域名创建的非单例
+ (instancetype)managerForAddress:(const void *)address;  // 使用指定Socket地址创建的非单例
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER; // 使用一个 SCNetworkReachabilityRef 目标引用来创建一个非单例
+ (instancetype)new NS_UNAVAILABLE; 
- (instancetype)init NS_UNAVAILABLE;

NS_UNAVAILABLE 修饰某个方法之后,我们在编码时,就不会自动显示这个方法了,如果强行使用,将会报错。这里作者将 new类方法和init 初始化方法都禁用,因此我们将不能再使用这两个方法。这个做法值得我们去借鉴。

还有一点,在一个类的的初始化方法中,如果我们希望指定开发者去调用某一个初始化方法时间,我们可以使用NS_DESIGNATED_INITIALIZER指定。 前提这个方法中会包含初始化与一个类时需要的所有参数。比如 -initWithReachability就是此类,这个方法中将包含了所有的需要的参数。

另外五个可用的方法我们从下往上看:

- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
    self = [super init];
    if (!self) {
        return nil;
    }

    _networkReachability = CFRetain(reachability);
    self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;

    return self;
} 

上面说了,AFNetworkReachabilityManager 是基于框架 SystemConfiguration实现网络监听的,这个方法中的 SCNetworkReachabilityRef就是SystemConfiguration框架下的网络监听目标的引用。得到SCNetworkReachabilityRef引用之后,AFNetworkReachabilityManager 使用一个变量 _networkReachability 保存。

_networkReachability

注意这里,使用了CFRetain(reachability);来赋值,原因是,_networkReachability本身并不是一个Cocoa对象,所以只能使用assign来修饰,而CFRetain可使得assign修饰的属性使用引用计数,作用相当于强引用。使用完之后,需要使用相应的CFRelease进行释放。

再说SCNetworkReachabilityRef这个引用,它有两类创建方式,一种是将域名作为监听目标,另一种是将Scoket地址作为监听目标,它一共有三个方法:

SCNetworkReachabilityRef __nullable
SCNetworkReachabilityCreateWithAddress      (
                        CFAllocatorRef          __nullable  allocator,
                        const struct sockaddr               *address
                        )               API_AVAILABLE(macos(10.3), ios(2.0));

SCNetworkReachabilityRef __nullable
SCNetworkReachabilityCreateWithAddressPair  (
                        CFAllocatorRef          __nullable  allocator,
                        const struct sockaddr       * __nullable    localAddress,
                        const struct sockaddr       * __nullable    remoteAddress
                        )               API_AVAILABLE(macos(10.3), ios(2.0));

SCNetworkReachabilityRef __nullable
SCNetworkReachabilityCreateWithName     (
                        CFAllocatorRef          __nullable  allocator,
                        const char                  *nodename
                        )               API_AVAILABLE(macos(10.3), ios(2.0));

第二种的方式和第一种其实是类似的,只是重点区分了本地的地址和远程的地址。
我们如果要使用 initWithReachability: 创建一个 AFNetworkReachabilityManager 的话,就必须使用上面的三个方法之一来构建其需要的参数了。

AFNetworkReachabilityManager 中,用了第一种和第三种。他们在两个分别用在两个初始化的方式中:

image.png

这里注意到,如果是非 Cocoa 的框架,我们需要对其产生的对象进行释放。如上面的 CFRelease(reachability);

我们在使用使用的,如果需要监听自家的网站域名,那么可以使用

[AFNetworkReachabilityManager managerForDomain:@"公司的服务器地址"];

这个方式来监听。这样有利于我们针对自己服务器的链接状态监听。相对来说更加精准。

AFNetworkReachabilityManager 默认情况下,是使用监听 Socket 地址的方式进行的监听的,从这个两个初始化方式中可以看到:

manager和sharedManager

源码中,可以看到, +sharedManager 实际上是对 +manager上的一个单例,而在+manager中,最终获取的是 + managerForAddress创建的实例。这其中创建了一个sockaddr_in结构体:

sockaddr_in结构体

+manager方法中并没有指明结构体的 port 和 addr,只是指明了内部的连接协议族 sin_family 为 AF_INET,意思是这个传输方式是TCP或者UDP等。其他的信息将会在SCNetworkReachabilityRef中补充默认值。
从这里就可以看到,当我们使用默认单例方法创建一个AFNetworkReachabilityManager 单例时,实际上就是以默认的Scoket地址作为监听的应用对象,获取网络变化。

3.2 开始监听

AFNetworkReachabilityManager 开启监听和停止监听分别由以下两个方法处理,它们的实现:

开始监听和停止监听

在开启监听的实现中,首先会停止网络监听,保证最终进入未监听的状态。然后定义了一个回调函数 callback,这个 callback 其实就是一个Block,他的类型是AFNetworkReachabilityStatusBlock,最终将会在网络变化时调用。
callback 内最终将执行对象的 networkReachabilityStatusBlock

image.png

我们可以通过

image.png

来设置这个对象的值。 达到往外调出的目的。

这里面还有一个很常见的技巧:作者在 block 中强引用了弱引用对象,目的是确保在执行block的过程中,就算对象在外部被释放,但也不会立刻销毁,而是保证block安全的执行完毕之后才销毁。

设置好回调用的 callback 之后,需要把callback 和网络的变化监听连接起来。下面这段代码的完成了这个功能:

image.png

我们发现这里使用了一个新的东西 —— SCNetworkReachabilityContext结构体:

image.png

网上找到一份详细的关于SCNetworkReachabilityContext的解释:

typedef struct {
    CFIndex version;
    // 创建一个 SCNetworkReachabilityContext 结构体时,需要调用 SCDynamicStore 的创建函数,而此创建函数会根据 version 来创建出不同的结构体,SCNetworkReachabilityContext 对应的 version 是 0;

    void * __nullable info;
    // A C pointer to a user-specified block of data. 用户指定的需要传递的数据快,下面两个 block(retain 和 release)的参数就是 info。如果 info 是一个 block 类型,需要调用下面定义的 retain 和 release 进行拷贝和释放;
    
    const void * __nonnull (* __nullable retain)(const void *info);
    // 该 retain block 用于对上述 info 进行 retain(一般通过调用 Block_copy 宏 retain 一个 block 函数,即在堆空间新建或直接引用一个 block 拷贝),该值可以为 NULL;
    
    void (* __nullable release)(const void *info);
    // 该 release block 用于对 info 进行 release(一般通过调用 Block_release 宏 release 一个 block 函数,即将 block 从堆空间移除或移除相应引用),该值可以为 NULL;
    
    CFStringRef __nonnull (* __nullable copyDescription)(const void *info);
    // 提供 info 的描述,一般取为 NULL。
} SCNetworkReachabilityContext;

作者:XcodeMen
链接:https://www.jianshu.com/p/fb3676a3d5f7
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

SCNetworkReachabilityContext结构体的作用是关联callbackSCNetworkReachabilityRef。两者都作为 SCNetworkReachabilitySetCallback 的入参进行关联。

但是我们最终还看到一个参数: AFNetworkReachabilityCallback ,这个其实和callback类似,但是,他不需要通过context进行承载,直接能够监听网络变化。
AFNetworkReachabilityCallback的实现:

AFNetworkReachabilityCallback实现

可以看到AFNetworkReachabilityCallback是用在发送全局通知上了。

有点绕,做个简单的小总结:

AFNetworkReachabilityStatusBlock 类型的 callback最终被加入SCNetworkReachabilityContext中监听网络变化,用在外部获取王网络状态的 block 执行。

AFNetworkReachabilityCallback直接用于监听网络变化,获取变化后,用户发送全局通知到外部。

最后,加入监听之后,立马做了一次通知中心的发送,发送当前的网络状态:

首次发送状态状态
3.2 停止监听

停止监听就比较简单了,直接将网络监听引用从 runloop 中移除就行了。


停止监听网络

以上就是对于AFNetworkReachabilityManager的全部阅读记录。 如有错误欢迎指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351