原文:http://itangqi.me/2016/05/17/the-notes-of-learning-afnetworking-six/
前言
AFNetworkReachabilityManager
是對 SystemConfiguration
模塊的封裝,蘋果的文檔中也有一個類似的項目
Reachability 這裏對網絡狀態的監控跟蘋果官方的實現幾乎是完全相同的。
同樣在 GitHub 上有一個類似的項目叫做 Reachability 不過這個項目由於命名的原因可能會在審覈時被拒絕。
無論是 AFNetworkReachabilityManager
,蘋果官方的項目或者說 GitHub 上的 Reachability,它們的實現都是類似的,而在這裏我們會以
AFNetworking
中的 AFNetworkReachabilityManager
爲例來說明在 iOS 開發中,我們是怎樣監控網絡狀態的。
AFNetworkReachabilityManager 的使用和實現
AFNetworkReachabilityManager
的使用還是非常簡單的,只需要三個步驟,就基本可以完成對網絡狀態的監控:
- 初始化
AFNetworkReachabilityManager
- 調用
startMonitoring
方法開始對網絡狀態進行監控 - 設置
networkReachabilityStatusBlock
在每次網絡狀態改變時, 調用這個 block
初始化 AFNetworkReachabilityManager
在初始化方法中,使用 SCNetworkReachabilityCreateWithAddress
或者 SCNetworkReachabilityCreateWithName
生成一個
SCNetworkReachabilityRef
的引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
+ (instancetype)managerForDomain:(NSString *)domain { SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]); AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; return manager; } + (instancetype)managerForAddress:(const void *)address { SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address); AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; return manager; } |
- 這兩個方法會通過一個域名或者一個
sockaddr_in
的指針生成一個SCNetworkReachabilityRef
- 調用
- [AFNetworkReachabilityManager initWithReachability:]
將生成的SCNetworkReachabilityRef
引用傳給networkReachability
- 設置一個默認的
networkReachabilityStatus
1 2 3 4 5 6 7 8 9 10 11 |
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability { self = [super init]; if (!self) { return nil; } self.networkReachability = CFBridgingRelease(reachability); self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown; return self; } |
當調用
CFBridgingRelease(reachability)
後,會把reachability
橋接成一個 NSObject 對象賦值給self.networkReachability
,然後釋放原來的 CoreFoundation 對象。
監控網絡狀態
在初始化 AFNetworkReachabilityManager
後,會調用 startMonitoring
方法開始監控網絡狀態:
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 |
- (void)startMonitoring { // 1. 先調用 `- stopMonitoring` 方法,如果之前設置過對網絡狀態的監聽,使用 `SCNetworkReachabilityUnscheduleFromRunLoop` 方法取消之前在 Main Runloop 中的監聽 [self stopMonitoring]; if (!self.networkReachability) { return; } __weak __typeof(self)weakSelf = self; // 2. 創建一個在每次網絡狀態改變時的回調 AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) { __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf.networkReachabilityStatus = status; if (strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); } }; id networkReachability = self.networkReachability; // 3. 創建一個 `SCNetworkReachabilityContext`,其中的 `callback` 就是上一步中的創建的 block 對象 SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL}; // 4. 當目標的網絡狀態改變時,會調用傳入的回調 SCNetworkReachabilitySetCallback((__bridge SCNetworkReachabilityRef)networkReachability, AFNetworkReachabilityCallback, &context); // 5. 在 Main Runloop 中對應的模式開始監控網絡狀態 SCNetworkReachabilityScheduleWithRunLoop((__bridge SCNetworkReachabilityRef)networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); // 6. 獲取當前的網絡狀態,調用 callback dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{ SCNetworkReachabilityFlags flags; if (SCNetworkReachabilityGetFlags((__bridge SCNetworkReachabilityRef)networkReachability, &flags)) { AFPostReachabilityStatusChange(flags, callback); } }); } |
1. 關於 AFNetworkReachabilityStatus
:
1 2 3 4 5 6 |
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) { AFNetworkReachabilityStatusUnknown = -1, AFNetworkReachabilityStatusNotReachable = 0, AFNetworkReachabilityStatusReachableViaWWAN = 1, AFNetworkReachabilityStatusReachableViaWiFi = 2, }; |
2. 關於 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; |
在下一節中會介紹上面所提到的一些 C 函數以及各種回調。
設置 networkReachabilityStatusBlock 以及回調
在 Main Runloop 中對網絡狀態進行監控之後,在每次網絡狀態改變,就會調用 AFNetworkReachabilityCallback
函數:
1 2 3 |
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info); } |
這裏會從 info
中取出之前存在 context
中的 AFNetworkReachabilityStatusBlock
。
1 2 3 4 5 6 7 8 9 10 |
__weak __typeof(self)weakSelf = self; AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) { __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf.networkReachabilityStatus = status; if (strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); } }; |
取出這個 block 之後,傳入 AFPostReachabilityStatusChange
函數:
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
獲取當前的網絡可達性狀態 - 在主線程中異步執行上面傳入的
callback
block(設置self
的網絡狀態,調用networkReachabilityStatusBlock
) - 發送
AFNetworkingReachabilityDidChangeNotification
通知.
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 |
static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) { // 該網絡地址可達 BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0); // 該網絡地址雖然可達,但是需要先建立一個 connection BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); // 該網絡雖然也需要先建立一個 connection,但是它是可以自動去 connect 的 BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0)); // 不需要用戶交互,就可以 connect 上(用戶交互一般指的是提供網絡的賬戶和密碼) BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0); // 如果 isReachable==YES,那麼就需要判斷是不是得先建立一個 connection,如果需要,那就認爲不可達,或者雖然需要先建立一個 connection,但是不需要用戶交互,那麼認爲也是可達的 BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction)); // AFNetworkReachabilityStatus 就四種狀態 Unknown、NotReachable、ReachableViaWWAN、ReachableViaWiFi,這四種狀態字面意思很好理解,這裏就不贅述了 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; } |
因爲 flags
是一個 SCNetworkReachabilityFlags
,它的不同位代表了不同的網絡可達性狀態,通過
flags
的位操作,獲取當前的狀態信息 AFNetworkReachabilityStatus
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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 }; |
這裏就是在 SystemConfiguration
中定義的全部的網絡狀態的標誌位。
與 AFNetworking 協作
其實 AFNetworkReachabilityManager
與 AFNetworking
整個框架並沒有太多的耦合。正相反,它在整個框架中作爲一個即插即用的類使用,每一個
AFURLSessionManager
都會持有一個 AFNetworkReachabilityManager
的實例。
1
|
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
|
這是整個框架中除了 AFNetworkReachabilityManager.h/m
文件,唯一一個引用到這個類的地方。
總結
AFNetworkReachabilityManager
實際上只是對底層SystemConfiguration
庫中的 C 函數封裝的類(類似於 GCD),它爲我們隱藏了 C 語言的實現,提供了統一且簡潔的 Objective-C 語言接口- 它是
AFNetworking
中一個即插即用的模塊