AFNetworking 原始碼閱讀之網路監聽 Reachability

AFNetWorking原始碼閱讀
AFNetWorking是一款用於 Cocoa 上的網路庫,它適用於 iOS, macOS, watchOS, 以及 tvOS 等各個系統。 AFNetWorking 的優點在於,它提供了一套非常全面並且易於使用的 API,讓我們在隔絕和 Cocoa 原生網路架構的繁瑣互動的過程中,編寫與網路相關的程式碼。
閱讀 AFNetWorking 原始碼不僅能讓開發者更好的理解和運用這個人氣超高的網路庫,還能從中學到許多優秀的開發技巧,感受大神的風采。
AFNetWorking原始碼中,主要包含了四大內容:
- Reachability 網路監聽
- NSURLSession 的封裝
- Security 安全策略
- Serialization 序列化
本文是對 Reachability 網路監聽模組 AFNetworkReachabilityManager 的原始碼閱讀做的記錄。
一、AFNetworkReachabilityManager 是如何使用的
AFNetworkReachabilityManager是 AFNetWorking 的一個子模組,實際上它能夠完全脫離 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
來修飾,
assign
沒有是直接持有,而
CFRetain
可使得
assign
修飾的屬性使用引用計數,作用相當於強引用。使用完之後,需要使用相應的
CFRelease
進行釋放。
再說 SCNetworkReachabilityRef
這個引用,它有兩類建立方式,一種是將域名作為監聽目標,另一種是將Scoket地址作為監聽目標,它一共有三個方法:
SCNetworkReachabilityRef __nullable SCNetworkReachabilityCreateWithAddress( CFAllocatorRef__nullableallocator, const struct sockaddr*address )API_AVAILABLE(macos(10.3), ios(2.0)); SCNetworkReachabilityRef __nullable SCNetworkReachabilityCreateWithAddressPair( CFAllocatorRef__nullableallocator, const struct sockaddr* __nullablelocalAddress, const struct sockaddr* __nullableremoteAddress )API_AVAILABLE(macos(10.3), ios(2.0)); SCNetworkReachabilityRef __nullable SCNetworkReachabilityCreateWithName( CFAllocatorRef__nullableallocator, 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
結構體的作用是關聯 callback
和 SCNetworkReachabilityRef
。兩者都作為 SCNetworkReachabilitySetCallback
的入參進行關聯。
但是我們最終還看到一個引數: AFNetworkReachabilityCallback
,這個其實和 callback
類似,但是,他不需要通過 context
進行承載,直接能夠監聽網路變化。
AFNetworkReachabilityCallback
的實現:

AFNetworkReachabilityCallback實現
AFNetworkReachabilityCallback
是用在傳送全域性通知上了。
有點繞,做個簡單的小總結:
AFNetworkReachabilityStatusBlock
型別的 callback
最終被加入 SCNetworkReachabilityContext
中監聽網路變化,用在外部獲取王網路狀態的 block 執行。
AFNetworkReachabilityCallback
直接用於監聽網路變化,獲取變化後,使用者傳送全域性通知到外部。
最後,加入監聽之後,立馬做了一次通知中心的傳送,傳送當前的網路狀態:

首次傳送狀態狀態
3.2 停止監聽
停止監聽就比較簡單了,直接將網路監聽引用從 runloop 中移除就行了。

停止監聽網路
以上就是對於 AFNetworkReachabilityManager 的全部閱讀記錄。 如有錯誤歡迎指正。