AFNetworking3源码阅读-Reachability

AFNetworking3源码阅读-Reachability

用于监控网络状态。当网络状态改变时,是重新发起一些请求的好时间;网络状态的监控也能给用户更好的网络失败提示。

AFNetworkReachabilityManager

使用

1
2
3
4
5
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status));
}];

[[AFNetworkReachabilityManager sharedManager] startMonitoring];

结构

AFNetworkReachabilityManager中大概有这么几个方法:

初始化

sharedManager 使用GCD dispatch_once_t单例化。具体实例调用 manager 方法。
+manager方法使用了这个函数来创建一个默认的,用于0.0.0.0的连接器

__IPHONE_OS_VERSION_MIN_REQUIRED 在iOS环境下(要求最低的系统版本);
__MAC_OS_X_VERSION_MIN_REQUIRED则表示mac环境下要求的最低环境
90000 表示 __IPHONE_9_0 ;101100 表示 __MAC_10_11

sockaddr_in6 代表ipv6地址
sockaddr_in 代表ipv4d地址
由于IPv6 是ios9和os_x 10.11后边推出的,这里要进行版本判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+ (instancetype)manager
{
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
struct sockaddr_in6 address;
bzero(&address, sizeof(address));
address.sin6_len = sizeof(address);
address.sin6_family = AF_INET6;
#else
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_len = sizeof(address);
address.sin_family = AF_INET;
#endif
return [self managerForAddress:&address];
}

通过SCNetworkReachabilityCreateWithAddress生成SCNetworkReachabilityRef对象,通过监控该对象,即可获得网络连接情况。

1
2
3
4
5
6
7
8
+ (instancetype)managerForAddress:(const void *)address {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
//在`initWithReachability `中CFRetain了reachability,所以这里需要CFRelease。
CFRelease(reachability);

return manager;
}

监听

开启监听后,获得连接状态会调用AFPostReachabilityStatusChange,在AFPostReachabilityStatusChange中调用回调,并发送通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (void)startMonitoring {
[self stopMonitoring];

if (!self.networkReachability) {
return;
}

//监听回调,同时调用networkReachabilityStatusBlock
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
//修改networkReachabilityStatus状态
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}

};
//监听需要的步骤:
//1. 新建上下文
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
//2. 设置回调,, 当self.networkReachability的网络状态变化时,告之callBack处理
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
//3. 将networkReachability加入RunLoop
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
//4. 获得连接状态,并调用回调
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
}

关闭监听

1
2
3
4
5
6
7
- (void)stopMonitoring {
if (!self.networkReachability) {
return;
}
//将networkReachability从RunLoop中移除
SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}

注册键值监听

重装keyPathsForValuesAffectingValueForKey,指明reachable 、reachableViaWWAN 和reachableViaWiFi属性依赖于networkReachabilityStatus。当networkReachabilityStatus改变时,观察reachable 、reachableViaWWAN 和reachableViaWiFi属性的程序需要被通知。

这是因为获得网络状态时,回调中只修改了networkReachabilityStatus值。

1
2
3
4
5
6
7
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if ([key isEqualToString:@"reachable"] || [key isEqualToString:@"reachableViaWWAN"] || [key isEqualToString:@"reachableViaWiFi"]) {
return [NSSet setWithObject:@"networkReachabilityStatus"];
}

return [super keyPathsForValuesAffectingValueForKey:key];
}

SCNetworkReachability

从上文可以看出监听主要使用的是SystemConfiguration框架中 SCNetworkReachability.h中的方法。这里介绍一下用到的方法和数据类型。

SCNetworkReachabilityFlags: 保存连接状态各个状态的含义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef CF_OPTIONS(uint32_t, SCNetworkReachabilityFlags) {
kSCNetworkReachabilityFlagsTransientConnection = 1<<0, // 可以通过瞬时连接(例如PPP---点对点通讯协议) 链接到给定的节点名和地址
kSCNetworkReachabilityFlagsReachable = 1<<1, // 当前网络配置可以请求到给定的节点名和地址
kSCNetworkReachabilityFlagsConnectionRequired = 1<<2, //当前网络配置可以请求到给定的节点名和地址,但必须先创建一个连接。如果创建这个标识,需要象征性地生成
kSCNetworkReachabilityFlagsConnectionOnTraffic = 1<<3, //当前网络配置可以请求到给定的节点名和地址,但必须先创建一个连接。定向到指定节点或地址的通信将发起该连接
kSCNetworkReachabilityFlagsInterventionRequired = 1<<4, //当前网络配置可以请求到给定的节点名和地址,但必须先创建一个连接。
//用户需要通过某些方式的介入来创建该连接,比如提供密码、认证口令等。
//一般的,只有在一个拨号通信的配置中,在自动连接过程中发生某些错误(比如没有拨号音,没有回应,无效的密码等),这种情况下,PPP通信将会终止连接直到用户介入。
kSCNetworkReachabilityFlagsConnectionOnDemand = 1<<5, //当前网络配置可以请求到给定的节点名和地址,但必须先创建一个连接。该请求一经CFSocketStream程序接口请求就创建,其他的函数不会创建该连接
kSCNetworkReachabilityFlagsIsLocalAddress = 1<<16, //所请求的节点或地址为连接到当前系统的网络节点
kSCNetworkReachabilityFlagsIsDirect = 1<<17, //网络通信不会通过网关连接给定的节点名和地址,而是直接路由到系统中的一个接口上
#if TARGET_OS_IPHONE
kSCNetworkReachabilityFlagsIsWWAN = 1<<18, // 可通过蜂窝数据网络访问给定的节点或地址
#endif // TARGET_OS_IPHONE

kSCNetworkReachabilityFlagsConnectionAutomatic = kSCNetworkReachabilityFlagsConnectionOnTraffic
};

SCNetworkReachabilityRef 用来测试连接的引用。有三种创建方式:

1
2
3
SCNetworkReachabilityCreateWithAddress(allocator,address)
SCNetworkReachabilityCreateWithAddressPair(allocator,localAddress,remoteAddress)
SCNetworkReachabilityCreateWithName(allocator,nodename)

SCNetworkReachabilityContext 上下文是一个结构体:

1
2
3
4
5
6
7
8
9
10
11
12
 typedef struct {
// 创建一个SCNetworkReachabilityContext结构体时,需要调用SCDynamicStore的创建函数,而此创建函数会根据version来创建出不同的结构体,SCNetworkReachabilityContext对应的version是0
CFIndex version;
// 下面两个block(release和retain)的参数就是info,此处表示的是网络状态处理的回调函数
void * __nullable info;
// 该retain block用于对info进行retain,下面那个AFNetworkReachabilityRetainCallback核心就是调用了Block_copy(用于retain一个block函数,即在堆空间新建或直接引用一个block拷贝)
const void * __nonnull (* __nullable retain)(const void *info);
// 该release block用于对info进行release,下面那个AFNetworkReachabilityReleaseCallback核心就是调用了Block_release(用于release一个block函数,即将block从堆空间移除或移除相应引用)
void (* __nullable release)(const void *info);
// 提供info的description,此处调用为NULL
CFStringRef __nonnull (* __nullable copyDescription)(const void *info);
} SCNetworkReachabilityContext;

SCNetworkReachabilitySetCallback 给客户端指定对应target(该参数和需要检测网络状况的地址有一定关联,此处使用的是self.networkReachability),然后当这个target的网络状态变化时,告之SCNetworkReachabilityCallBack对象callBack处理(此处使用的是AFNetworkReachabilityCallback),另外callBack中使用到的参数包括target和context提供的info。

1
2
3
4
5
6
Boolean
SCNetworkReachabilitySetCallback (
SCNetworkReachabilityRef target,
SCNetworkReachabilityCallBack __nullable callout,
SCNetworkReachabilityContext * __nullable context
)