AFNetworkReachabilityManager 閱讀筆記
AFNetworkReachabilityManager 是 AFNetworking 中用來監聽網絡可達性的組件,(有沒有網絡,什么網絡)之類的。
ps:不能根據這個狀態來阻止用戶發送網絡請求。
我們項目中,對網絡狀態變化做了一層封裝,如下:
@interface PTVNetworkStatus()
@property(strong,nonatomic)AFHTTPSessionManager* session;
@end
@implementation PTVNetworkStatus
-(id)init
{
if (self = [super init]) {
_session = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"https://api.m.panda.tv"]];
_session.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
[_session.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"-------AFNetworkReachabilityStatusReachableViaWWAN------");
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"-------AFNetworkReachabilityStatusReachableViaWiFi------");
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"-------AFNetworkReachabilityStatusNotReachable------");
break;
default:
break;
}
if (_status != status) {
_status = status;
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:nil];
}
}];
[_session.reachabilityManager startMonitoring];
}
return self;
}
我覺得這個包裝有問題。所以決定深入看一下代碼,
_session = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"https://xxxxx.xxxxx"]];
_session.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
這里initWithBaseUrl 的參數 https://xxxxx.xxxxx 對網絡狀態監聽并沒有影響。
糾錯
我們看看 AFHTTPSessionManager 的初始化函數:
在 AFURLSessionManager 的 init 函數中有默認的 reachabilityManager 實現。
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
默認的實現是不會用到剛才傳入的參數的。
+ (instancetype)sharedManager {
static AFNetworkReachabilityManager *_sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_len = sizeof(address);
address.sin_family = AF_INET;
_sharedManager = [self managerForAddress:&address];
});
return _sharedManager;
}
所以項目中的正確用法應該是使用 managerForDomain 函數進行初始化 AFNetworkReachabilityManager 對象。然后使用 setReachabilityStatusChangeBlock 監聽網絡狀態。。。
比如:
AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager managerForDomain:@"www.google.com"];
[manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status){
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"Never called");
break;
default:
NSLog(@"Never called");
break;
}
}];
[manager startMonitoring];
如果項目要包裝 AFNetworkReachabilityManager 也不建議在 block 中直接發送通知,因為都在同一個類,如果通知忘記清空,倒是還有可能導致崩潰。
建議使用target/action 的方式,包裝!一個列表,足以維護某個類需要的網絡請求。
AFNetworkReachabilityManager 實現原理。
主要是基于 SCNetworkReachabilityRef 對網絡狀態進行監聽的,系統本身已經支持監聽網絡狀態,只是C 語言的形式,加上函數指針等等,對iOS 開發者,并不是很友好使用起來。
所以弄懂 AFNetworkReachabilityManager 只要點擊看看 SCNetworkReachabilityRef 的相關函數以及文檔就好。。
+ (instancetype)managerForDomain:(NSString *)domain {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
manager.networkReachabilityAssociation = AFNetworkReachabilityForName;
return manager;
}
上面的函數式依據domain 創建一個 監聽網絡的對象。首先先創建一個 SCNetworkReachabilityRef 引用。
SCNetworkReachabilityRef的官方說明如下:
The SCNetworkReachability API allows an application to
determine the status of a system's current network
configuration and the reachability of a target host.
In addition, reachability can be monitored with notifications
that are sent when the status has changed.
重點在于 函數 startMonitoring
- (void)startMonitoring {
//先停止當前的監聽
[self stopMonitoring];
///如果沒有 SCNetworkReachabilityRef 不能監聽
if (!self.networkReachability) {
return;
}
///創建網絡變化回調的block
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
id networkReachability = self.networkReachability;
///創建 SCNetworkReachabilityContext 結構,結構包含了用戶指定的信息,和回調函數
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
///設置網絡變化的回調
SCNetworkReachabilitySetCallback((__bridge SCNetworkReachabilityRef)networkReachability, AFNetworkReachabilityCallback, &context);
///指定網絡監聽的runloop
SCNetworkReachabilityScheduleWithRunLoop((__bridge SCNetworkReachabilityRef)networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
switch (self.networkReachabilityAssociation) {
case AFNetworkReachabilityForName:
break;
case AFNetworkReachabilityForAddress:
case AFNetworkReachabilityForAddressPair:
default: {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
SCNetworkReachabilityGetFlags((__bridge SCNetworkReachabilityRef)networkReachability, &flags);
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
dispatch_async(dispatch_get_main_queue(), ^{
callback(status);
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:@{ AFNetworkingReachabilityNotificationStatusItem: @(status) }];
});
});
}
break;
}
}
開始監聽函數的實現過程。
停止監聽的過程如下:
- (void)stopMonitoring {
if (!self.networkReachability) {
return;
}
SCNetworkReachabilityUnscheduleFromRunLoop((__bridge SCNetworkReachabilityRef)self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}
直接從runloop中移除當前的網絡監聽對象、
小結
使用第三方功能的時候,一定要對源碼有足夠的了解才行。