SCNetworkReachability API(或-怎樣檢查 iPhone 的網路狀態)

要寫一套 iPhone 軟體,難免會在軟體中用到網路功能-畢竟 iPhone 就是一台讓你帶著走隨時隨地都可以連上網路的裝置;而既然在軟體中會用到網路,就必定需要檢查目前的網路狀態,在沒有網路連線的狀況下,至少要告訴使用者裝置有沒有連線之類的。

要檢查網路連線狀態似乎很簡單:反正總是要發個連線 request 到我們要連線的地方,有回應就是網路連得上,沒回應就是連不上嘛。

不過,因為蘋果一些貼心的考量,就把事情弄得有些複雜。蘋果說,你不但要檢查有沒有網路,你還要檢查使用者目前使用的是哪種-Wifi 無線網路或是 3G/EDGE 等電信商網路,如果你的軟體的功能是傳輸大量的檔案-比方說讓人家一個按鈕就下載幾十 MB 的音樂檔案之類的-就要提示使用者,如果使用電信商網路傳輸這麼多檔案,很可能月底電話費會暴增之類,最好還要做個什麼偏好設定,讓使用者決定要不要開啟電信商網路…云云。

如果不這麼做,蘋果便可能以因為會造成使用者困擾這樣的理由,拒絕你的軟體在 AppStore 上架。-會這麼說就代表,嗯,我有遇到過。

蘋果通常建議你去看 iPhone SDK 裡頭附帶的一組叫 Reachability 的範例程式,叫你用裡頭一組就叫做 Reachability 的 singleton helper class,這個 class 基本上是把實際用來檢查網路狀態的 C API-叫做 SCNetworkReachability-用 Objective C 包裝成一個物件,最主要在做事的,還是在 SCNetworkReachability 這一層。

實際使用時,會發現 Reachability 這個 class 在設計上,忽略了 iPhone 與 iPod Touch 的某些行為,而造成一些問題。

最大的問題是,當使用者在使用你的應用程式的時候,如果關掉螢幕,將裝置放在鎖定狀態(就是那個重新打開時需要在螢幕上畫一下「解鎖」的狀態)一陣子,然後再按一下按鈕恢復使用,這個時候你想要做一些網路操作,其實是可以連線,但是 Reachability API 還是告訴你無法連線;或剛打開應用程式的時候,也告訴你無法連線。當你需要判斷能不能連線,使用者用了哪種連線而應不應該繼續連線的時候,其實使用者可以連線,API 卻始終一直告訴你不能連線。

把裝置設定為鎖定,然後恢復使用的狀況是這樣的-蘋果的設計是,為了節省電力消耗,會在進入鎖定狀態後,自動關閉無線網路介面,而當你解除鎖定後,才會把無線網路再度打開。而 Reachability 基本上只詢問「目前的網路狀態」,如果裝置的無線網路正處於從關閉的狀態恢復的階段,這時後回傳的結果便是無法連線。

順道一提,在第三方應用程式中,可以在 Info.plist 檔案中,設定 UIRequiresPersistentWifi 這個選項,文件中說這個設定可以讓應用程式持續保持無線網路的連線狀態,但是,就算設了這項設定,在進入鎖定狀態後,系統仍然會自動關閉無線網路介面,這項設定僅侷限於 iPhone 一直開著、你不去把螢幕關掉的狀況。

至於怎樣在鎖定狀態下繼續保持連線,那又是另外一個話題了。

剛進入應用程式的時候,也往往回傳無法連線-猜測應該是使用 iPhone 主畫面(Springboard)的時候,無線網路介面大概也是關閉的。在點選要用什麼應用程式的時候,好像也用不到什麼網路功能,為了節電把網路介面關了也好。至於定時檢查信箱、或從 AppStore 下載軟體什麼的,應該是蘋果有其他自己的背景程序,負責呼叫網路介面。

簡言之,就是你常會遇到「問的時候說沒有,但是下一秒鐘網路就通」的狀況,遇到這種狀況,要使用比較白爛的作法,可以向 Reachability 連續問兩次,如果第一次說沒有第二次卻說有,那就代表其實還是有網路…可能比較好的作法,可以是,我們不要叫一個 method 直接回傳給我們網路狀態,而是變成 delegate 的方式來處理。

流程大概是這樣,這邊稍微有些囉嗦-

  1. 設計兩個 delegate method,分別用於有網路與沒網路兩種狀況。
  2. 先生出一個 SCNetworkReachabilityRef 物件,然後用 SCNetworkReachabilityGetFlags() 抓取目前的網路狀態,如果是有,就呼叫「有網路」的那組 delegate method,直接結束。
  3. 如果這一次抓取網路狀態的結果是沒有連線,我們就對剛剛產生的 SCNetworkReachabilityRef 物件設定一個 SCNetworkReachabilityCallBack function。因為只要連線狀態出現變化,就會呼叫這個 function,所以,在呼叫到的時候,再用 SCNetworkReachabilityGetFlags() 抓一次目前的網路狀態,決定要回傳是「有網路」或「沒網路」的 delegate method。如果有呼叫到,通常是會有,如此一來,我們可以捕捉到了「第一次說沒有,但是後來又有網路」的狀況,並且成功回傳「有網路」。
  4. 同時設一組 timer(或用 NSObject 的 perform selector after delay 之類的),如果超過一段時間,SCNetworkReachability API 都沒有被呼叫前一點中提到的 SCNetworkReachabilityCallBack function,就代表不但一開始沒網路,而且後來一直還是那個狀態,那…就代表一直沒網路。這時後呼叫「沒網路」的 delegate method。
  5. 記得要 release 那個 SCNetworkReachabilityRef 物件…。

採用這種實作,在沒有網路連線的狀態下,就會需要幾秒鐘的等待,確定目前的確沒有網路連線。不過嘛,反正沒有網路連線,也不能夠做什麼別的事情,所以等個幾秒鐘也無所謂嘛。

順道一提,iPhone SDK 關於 SCNetworkReachability 的說明文件裡頭有個錯誤,在講如何設定 SCNetworkReachabilityCallBack 的部份提到-

Here is an example of a function declared to conform to this type:

– (void)MyReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info);

你到底是要寫 C function 還是要寫 Objective C 的 instance method?(翻桌)

2 thoughts on “SCNetworkReachability API(或-怎樣檢查 iPhone 的網路狀態)

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.