AFNetworking 源碼閱讀之網絡監聽 Reachability

AFNetWorking源碼閱讀

AFNetWorking 是一款用于 Cocoa 上的網絡庫,它適用于 iOS, macOS, watchOS, 以及 tvOS 等各個系統。AFNetWorking 的優點在于,它提供了一套非常全面并且易于使用的 API,讓我們在隔絕和 Cocoa 原生網絡架構的繁瑣交互的過程中,編寫與網絡相關的代碼。

閱讀 AFNetWorking 源碼不僅能讓開發者更好的理解和運用這個人氣超高的網絡庫,還能從中學到許多優秀的開發技巧,感受大神的風采。

AFNetWorking 源碼中,主要包含了四大內容:

  • Reachability 網絡監聽
  • NSURLSession 的封裝
  • Security 安全策略
  • Serialization 序列化

本文是對 Reachability 網絡監聽模塊AFNetworkReachabilityManager 的源碼閱讀做的記錄。

一、AFNetworkReachabilityManager 是如何使用的

AFNetworkReachabilityManagerAFNetWorking 的一個子模塊,實際上它能夠完全脫離 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來修飾,而CFRetain可使得assign修飾的屬性使用引用計數,作用相當于強引用。使用完之后,需要使用相應的CFRelease進行釋放。

再說SCNetworkReachabilityRef這個引用,它有兩類創建方式,一種是將域名作為監聽目標,另一種是將Scoket地址作為監聽目標,它一共有三個方法:

SCNetworkReachabilityRef __nullable
SCNetworkReachabilityCreateWithAddress      (
                        CFAllocatorRef          __nullable  allocator,
                        const struct sockaddr               *address
                        )               API_AVAILABLE(macos(10.3), ios(2.0));

SCNetworkReachabilityRef __nullable
SCNetworkReachabilityCreateWithAddressPair  (
                        CFAllocatorRef          __nullable  allocator,
                        const struct sockaddr       * __nullable    localAddress,
                        const struct sockaddr       * __nullable    remoteAddress
                        )               API_AVAILABLE(macos(10.3), ios(2.0));

SCNetworkReachabilityRef __nullable
SCNetworkReachabilityCreateWithName     (
                        CFAllocatorRef          __nullable  allocator,
                        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
鏈接:http://www.lxweimin.com/p/fb3676a3d5f7
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。

SCNetworkReachabilityContext結構體的作用是關聯callbackSCNetworkReachabilityRef。兩者都作為 SCNetworkReachabilitySetCallback 的入參進行關聯。

但是我們最終還看到一個參數: AFNetworkReachabilityCallback ,這個其實和callback類似,但是,他不需要通過context進行承載,直接能夠監聽網絡變化。
AFNetworkReachabilityCallback的實現:

AFNetworkReachabilityCallback實現

可以看到AFNetworkReachabilityCallback是用在發送全局通知上了。

有點繞,做個簡單的小總結:

AFNetworkReachabilityStatusBlock 類型的 callback最終被加入SCNetworkReachabilityContext中監聽網絡變化,用在外部獲取王網絡狀態的 block 執行。

AFNetworkReachabilityCallback直接用于監聽網絡變化,獲取變化后,用戶發送全局通知到外部。

最后,加入監聽之后,立馬做了一次通知中心的發送,發送當前的網絡狀態:

首次發送狀態狀態
3.2 停止監聽

停止監聽就比較簡單了,直接將網絡監聽引用從 runloop 中移除就行了。


停止監聽網絡

以上就是對于AFNetworkReachabilityManager的全部閱讀記錄。 如有錯誤歡迎指正。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373