蘋果示例原始碼閱讀:Reachability

發表於2016-10-14

本文由我們團隊的 康祖彬 童鞋撰寫,在他的個人主頁也可以看到這篇文章:https://kangzubin.cn


Reachability 是蘋果官方提供的示例原始碼,它是對 SystemConfiguration.framework 模組中的 SCNetworkReachability.h 標頭檔案裡提供的一系列網路連線狀態相關的 C 函式進行簡單封裝,以示範如何在 iOS App 開發中實現網路狀態變化監聽,由此也衍生出各種 Reachability 框架,比較著名的有 Github 上的 tonymillion/Reachability 以及 AFNetworking 中的 AFNetworkReachabilityManager 模組,它們的實現原理基本上是完全相同的。

下面我們就來閱讀分析一下蘋果提供的 Reachability 原始碼,原始碼中最核心的就 Reachability.hReachability.m 兩個檔案。

初始化方法

Reachability 中提供了三個快速初始化方法,分別為 reachabilityWithHostName:reachabilityWithAddress:reachabilityForInternetConnection

reachabilityWithHostName: 方法

該方法通過指定的 伺服器域名 初始化一個 Reachability 物件以進行判斷網路連線狀態,原始碼如下:

分析:上述程式碼比較簡單,通過呼叫 SCNetworkReachabilityCreateWithName C 函式生成一個 SCNetworkReachabilityRef 引用,然後初始化一個 Reachability 物件,並把剛才生成的引用賦給該物件中的 _reachabilityRef 成員變數,以供後面網路狀態監聽使用。

reachabilityWithAddress: 方法

該方法通過指定的 伺服器 IP 地址 初始化一個 Reachability 物件以進行判斷網路連線狀態,原始碼如下:

分析:與上述類似,該方法通過呼叫 SCNetworkReachabilityCreateWithAddress C 函式生成一個 SCNetworkReachabilityRef 引用,並賦給 Reachability 物件中的 _reachabilityRef 成員變數。

reachabilityForInternetConnection 方法

該方法通過 預設的路由地址 初始化一個 Reachability 物件以進行判斷網路連線狀態,通常用於 App 沒有連線到特定主機的情況,原始碼如下:

分析:在該方法中先初始化一個預設的 sockaddr_in Socket 地址(這裡建立的為零地址,0.0.0.0 地址表示查詢本機的網路連線狀態),然後呼叫 reachabilityWithAddress: 方法返回一個 Reachability 物件。

網路狀態監聽

開始監聽

通過上述初始化方法獲得一個 Reachability 物件後,可呼叫 startNotifier 方法開始進行網路狀態變化的監聽,原始碼如下:

關於 SCNetworkReachabilityContext 的定義和註釋如下:

此處 Reachability 示例程式碼中建立的 context 的 info 取的是物件本身 self(Reachability 物件型別),不是 block 型別,所以後面 retainrelease 兩個引數都取 NULL,關於 SCNetworkReachabilityContext 的詳細用法可參見 AFNetworkReachabilityManager.m

另外,上述回撥函式 ReachabilityCallback 的定義如下,在該回撥函式中,首先獲取一個 Reachability 物件,並把該物件作為引數傳送一個全域性通知,因此我們可以監聽 kReachabilityChangedNotification 通知以獲得實時網路連線狀態的變化。

SCNetworkReachabilityCallBack 規定了自定義的回撥函式的引數需要滿足如下形式:

取消監聽

我們可呼叫 Reachability 物件的 stopNotifier 進行取消網路連線狀態變化的監聽,原始碼如下:

釋放物件

當要釋放一個 Reachability 物件時,我們需要在其 dealloc 方法裡取消網路狀態監聽。另外由於 SCNetworkReachabilityRefCore Foundation 物件,所以這裡需要呼叫 CFRelease() 函式釋放 _reachabilityRef。

獲取當前網路連線狀態

當通過上述方法初始化一個 Reachability 物件並呼叫 startNotifier 方法開始監聽後,我們可以隨時呼叫物件的 currentReachabilityStatus 方法獲取當前網路連線狀態,返回的狀態型別 NetworkStatus 定義如下:

currentReachabilityStatus 方法的實現原始碼如下,首先通過呼叫 SCNetworkReachabilityGetFlags(...) 函式並傳入 _reachabilityRef 引用作為引數,獲得一個表示當前網路連線狀態的 SCNetworkReachabilityFlags 列舉值,然後根據列舉值呼叫 networkStatusForFlags: 方法判斷當前網路狀態型別並返回。

networkStatusForFlags: 方法根據具體的 SCNetworkReachabilityFlags 列舉值,判斷當前是否有網路連線,並且連線型別是 WiFi 還是 WWAN,具體實現和註釋如下:

在上述 networkStatusForFlags: 方法中,先呼叫了 PrintReachabilityFlags 函式列印當前網路連線狀態對應的 flags 字元,根據拼接的不同字元我們可以判斷不同的網路連線型別,比如 WiFi、2G、3G 等,該函式的實現如下:

比如,當是 WiFi 連線時會列印 “R”(這裡忽略 “-” 字元),當是 3G 連線時,列印 “Rt”,當是聯通或移動 2G 連線時,則列印 “Rtc” 等等。

另外,在 Reachability 類中,還提供了一個 connectionRequired 方法,用於判斷網路是否需要進一步連線(例如,雖然裝置的 WWAN 連線可用,但並沒有啟用,需要建立一個連線來啟用;或者雖然已連線上 WiFi,但該 WiFi 需要進一步 VPN 連線等情況),該方法通過驗證 SCNetworkReachabilityFlags 值是否為 kSCNetworkReachabilityFlagsConnectionRequired 判斷,實現如下:

使用示例

Reachability 原始碼的 APLViewController.m 檔案中,蘋果給出了上述封裝的使用示例。在我們的 App 開發中,我們可以按如下步驟獲取當前網路連線型別或者監聽網路連線變化:

總結

通過分析上述 Reachability 原始碼,我們可以總結 SCNetworkReachability.h 標頭檔案裡提供的一系列網路連線狀態相關的 C 函式的使用流程如下:

2376458-f9c0cda54706fb2a

SCNetworkReachability 使用示意圖
  1. 首先在 SCNetworkReachabilityCreateWithName(...)SCNetworkReachabilityCreateWithAddress(...)SCNetworkReachabilityCreateWithAddressPair(...) 3個初始化函式中任選其一建立一個 SCNetworkReachabilityRef 引用;
  2. 其次根據 SCNetworkReachabilityCallBack 定義一個網路監聽回撥函式,並初始化一個 SCNetworkReachabilityContext 上下文資訊,然後呼叫 SCNetworkReachabilitySetCallback 函式並傳入上述 ref、callback、context 3個引數,設定上述建立的 ref 在網路狀態發生變化時的回撥函式;
  3. 通過呼叫 SCNetworkReachabilityScheduleWithRunLoop(...)SCNetworkReachabilityUnscheduleFromRunLoop(...) 函式並傳入上述 ref,在 Current Runloop 中開始或取消監聽網路連線狀態變化,另外也可以通過 SCNetworkReachabilitySetDispatchQueue(...) 函式設定在指定執行緒裡監聽;
  4. 呼叫 SCNetworkReachabilityGetFlags(...) 函式並傳入上述 ref,可獲得當前網路連線狀態的 flags 列舉值,另外需要注意的是,當 DNS 伺服器無法連線,或者在弱網環境下,此函式將會很耗時,所以蘋果建議在子執行緒裡非同步呼叫此函式;
  5. 根據不同的 SCNetworkReachabilityFlags 列舉值,判斷當前網路連線狀態和連線型別。

Reference

相關文章