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
源碼中我添加了一些注釋:
這四個狀態將表示整個網絡監聽功能的最終結果。
2. 兩個網絡通知的常量字段和一個網絡狀態描述的回調函數
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);
它的內部實現這樣的:
我們能夠通過對這些文字進行本地化處理,獲取到的將是具有本地化信息的文字了。
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
保存。
注意這里,使用了
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 中,用了第一種和第三種。他們在兩個分別用在兩個初始化的方式中:
這里注意到,如果是非 Cocoa 的框架,我們需要對其產生的對象進行釋放。如上面的 CFRelease(reachability);
。
我們在使用使用的,如果需要監聽自家的網站域名,那么可以使用
[AFNetworkReachabilityManager managerForDomain:@"公司的服務器地址"];
這個方式來監聽。這樣有利于我們針對自己服務器的鏈接狀態監聽。相對來說更加精準。
AFNetworkReachabilityManager 默認情況下,是使用監聽 Socket 地址的方式進行的監聽的,從這個兩個初始化方式中可以看到:
源碼中,可以看到,
+sharedManager
實際上是對 +manager
上的一個單例,而在+manager
中,最終獲取的是 + managerForAddress
創建的實例。這其中創建了一個sockaddr_in
結構體:
+manager
方法中并沒有指明結構體的 port 和 addr,只是指明了內部的連接協議族 sin_family 為 AF_INET
,意思是這個傳輸方式是TCP或者UDP等。其他的信息將會在SCNetworkReachabilityRef
中補充默認值。
從這里就可以看到,當我們使用默認單例方法創建一個AFNetworkReachabilityManager 單例時,實際上就是以默認的Scoket地址作為監聽的應用對象,獲取網絡變化。
3.2 開始監聽
AFNetworkReachabilityManager 開啟監聽和停止監聽分別由以下兩個方法處理,它們的實現:
在開啟監聽的實現中,首先會停止網絡監聽,保證最終進入未監聽的狀態。然后定義了一個回調函數 callback
,這個 callback
其實就是一個Block,他的類型是AFNetworkReachabilityStatusBlock
,最終將會在網絡變化時調用。
callback
內最終將執行對象的 networkReachabilityStatusBlock
:
我們可以通過
來設置這個對象的值。 達到往外調出的目的。
這里面還有一個很常見的技巧:作者在 block 中強引用了弱引用對象,目的是確保在執行block的過程中,就算對象在外部被釋放,但也不會立刻銷毀,而是保證block安全的執行完畢之后才銷毀。
設置好回調用的 callback
之后,需要把callback
和網絡的變化監聽連接起來。下面這段代碼的完成了這個功能:
我們發現這里使用了一個新的東西 —— SCNetworkReachabilityContext
結構體:
網上找到一份詳細的關于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
結構體的作用是關聯callback
和SCNetworkReachabilityRef
。兩者都作為 SCNetworkReachabilitySetCallback
的入參進行關聯。
但是我們最終還看到一個參數: AFNetworkReachabilityCallback
,這個其實和callback
類似,但是,他不需要通過context
進行承載,直接能夠監聽網絡變化。
AFNetworkReachabilityCallback
的實現:
可以看到
AFNetworkReachabilityCallback
是用在發送全局通知上了。
有點繞,做個簡單的小總結:
AFNetworkReachabilityStatusBlock
類型的callback
最終被加入SCNetworkReachabilityContext
中監聽網絡變化,用在外部獲取王網絡狀態的 block 執行。
AFNetworkReachabilityCallback
直接用于監聽網絡變化,獲取變化后,用戶發送全局通知到外部。
最后,加入監聽之后,立馬做了一次通知中心的發送,發送當前的網絡狀態:
3.2 停止監聽
停止監聽就比較簡單了,直接將網絡監聽引用從 runloop 中移除就行了。
以上就是對于AFNetworkReachabilityManager的全部閱讀記錄。 如有錯誤歡迎指正。