AFNetworkReachabilityManager源码分析

AFNetworkReachabilityManager是AFNetworking框架中通过监控蜂窝网络和wifi网络接口的域名和地址的可达性的网络状态管理类。它其实是封装了底层SystemConfiguration类,使得网络监控变得像一个插件一样,简单易用。

AFNetworkReachabilityManager分析

首先我们看一下头文件中的网络状态枚举。它里面有4种状态值,分为未知、网络不可达、蜂窝网络和Wifi。

1
2
3
4
5
6
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
AFNetworkReachabilityStatusUnknown = -1,
AFNetworkReachabilityStatusNotReachable = 0,
AFNetworkReachabilityStatusReachableViaWWAN = 1,
AFNetworkReachabilityStatusReachableViaWiFi = 2,
};

然后我们看到一个宏组合。这表示在这两个宏之间的指针都是非空的。如果需要指定可以为空,则需单独指定nullable参数。

1
2
NS_ASSUME_NONNULL_BEGIN
NS_ASSUME_NONNULL_END

接着我们看到4个只读的属性值。其含义在代码中写的很清楚。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 The current network reachability status.
*/
@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;

/**
Whether or not the network is currently reachable.
*/
@property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;

/**
Whether or not the network is currently reachable via WWAN.
*/
@property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;

/**
Whether or not the network is currently reachable via WiFi.
*/
@property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;

后面三个属性的判断实现也是非常简单的。通过比对networkReachabilityStatus的状态来判断。

1
2
3
4
5
6
7
8
9
10
11
- (BOOL)isReachable {
return [self isReachableViaWWAN] || [self isReachableViaWiFi];
}

- (BOOL)isReachableViaWWAN {
return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWWAN;
}

- (BOOL)isReachableViaWiFi {
return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWiFi;
}

接着我们看到+ (instancetype)sharedManager这个方法。这个方法一看就是一个单例方法。然后我们m文件中看实现。内部用了dispatch_once_t实现单例。

1
2
3
4
5
6
7
8
9
+ (instancetype)sharedManager {
static AFNetworkReachabilityManager *_sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedManager = [self manager];
});

return _sharedManager;
}

dispatch_once中调用了+ (instancetype)manager方法。这里区分了新老版本,新版本socket地址使用ipv6,老版本使用ipv4。

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];
}

+ (instancetype)manager的最后调用了+ (instancetype)managerForAddress:(const void *)addressSCNetworkReachabilityRef是里面检测网络状态最核心的句柄,它通过刚才传入的地址初始化。另外SCNetworkReachabilityRef由于属于底层框架,不属于NSObject,需要手动管理引用计数。此外- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability中设置了networkReachabilityStatus的初始化值为AFNetworkReachabilityStatusUnknown,同时把reachability赋给了networkReachability。这个networkReachability很关键,后面的逻辑判断都会用到它。到这里初始化就结束了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
+ (instancetype)managerForAddress:(const void *)address {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];

CFRelease(reachability);

return manager;
}

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

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

return self;
}

另外,它还提供了从域名初始化,与从socket address初始化类似。只是没有提供相应的单例初始化方法。

1
2
3
4
5
6
7
8
9
+ (instancetype)managerForDomain:(NSString *)domain {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);

AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];

CFRelease(reachability);

return manager;
}

看完了初始化,我们来到重头戏- (void)startMonitoring。在这个类的说明中指出,要检测网络状态,必须先执行这个方法。然后我们来看一下其内部逻辑。
1.执行了停止原先监控的操作。其内部就是退出runloop操作。
2.检查networkReachability是否存在,如果不存在,直接返回。
3.创建了一个callback的block回调,就是为了把网络状态通过networkReachabilityStatusBlock回调出去。这里面为了防止循环引用,引入了weak-strong dance。
4.创建了SCNetworkReachabilityContext,引入了上一步创建的callback。
5.设置了SCNetworkReachabilitySetCallback。当网络状态改变时会进入AFNetworkReachabilityCallback
6.通过SCNetworkReachabilityScheduleWithRunLoop把这个检测任务丢入主runloop。
7.发起了一个异步优先级低的线程获取当前网络状态,并回调给callback。

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
- (void)startMonitoring {
[self stopMonitoring];

if (!self.networkReachability) {
return;
}

__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;

strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}

};

SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
}

我们来看一下上面第5步用到的AFNetworkReachabilityCallback方法,这个一个静态方法,其中有三个参数target、flags和info。其中target和info就是SCNetworkReachabilitySetCallback中传入的self.networkReachability&context

1
2
3
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}

然后看到AFNetworkReachabilityCallback内部调用了另一个静态方法AFPostReachabilityStatusChange。这里通过AFNetworkReachabilityStatusForFlags方法来判断flags对应的网络状态。然后把状态通过block回调出去。同时还发了一个change的通知。

1
2
3
4
5
6
7
8
9
10
11
static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block(status);
}
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
});
}

AFNetworkReachabilityStatusForFlags方法来判断状态其实很简单。SCNetworkReachabilityFlags是一个枚举值,通过综合判断flags的状态,就可以得出网络是否可达,是wifi网络还是蜂窝网络的判断。

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
34
35
36
37
typedef CF_OPTIONS(uint32_t, SCNetworkReachabilityFlags) {
kSCNetworkReachabilityFlagsTransientConnection = 1<<0,
kSCNetworkReachabilityFlagsReachable = 1<<1,
kSCNetworkReachabilityFlagsConnectionRequired = 1<<2,
kSCNetworkReachabilityFlagsConnectionOnTraffic = 1<<3,
kSCNetworkReachabilityFlagsInterventionRequired = 1<<4,
kSCNetworkReachabilityFlagsConnectionOnDemand = 1<<5, // __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_3_0)
kSCNetworkReachabilityFlagsIsLocalAddress = 1<<16,
kSCNetworkReachabilityFlagsIsDirect = 1<<17,
#if TARGET_OS_IPHONE
kSCNetworkReachabilityFlagsIsWWAN = 1<<18,
#endif // TARGET_OS_IPHONE

kSCNetworkReachabilityFlagsConnectionAutomatic = kSCNetworkReachabilityFlagsConnectionOnTraffic
};
static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) {
BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0);
BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0);
BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0));
BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0);
BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction));

AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown;
if (isNetworkReachable == NO) {
status = AFNetworkReachabilityStatusNotReachable;
}
#if TARGET_OS_IPHONE
else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
status = AFNetworkReachabilityStatusReachableViaWWAN;
}
#endif
else {
status = AFNetworkReachabilityStatusReachableViaWiFi;
}

return status;
}

这样整个- (void)startMonitoring就结束了。然后我们来看一下- (void)stopMonitoring停止监控的方法。
1.判断networkReachability是否为空,如果为空,则直接返回。
2.通过SCNetworkReachabilityUnscheduleFromRunLoop退出主runloop。

1
2
3
4
5
6
7
- (void)stopMonitoring {
if (!self.networkReachability) {
return;
}

SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}

结语

至此,整个AFNetworkReachabilityManager类的分析就结束了。我们可以看到,它通过优雅的封装,使得网络状态的判断变得十分方便。另外私有函数都是通过静态函数的方法呈现。

参考

AFNetworking
AFNetworkReachabilityManager 监控网络状态(四)

Author: MrHook
Link: https://bigjar.github.io/2018/04/11/AFNetworkReachabilityManager%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.