iOS 12+ 中檢測網路訪問

知識小集發表於2019-01-16

作者:Ross Butler

原文連結:medium.com/@rwbutler/n…

公眾號連結:mp.weixin.qq.com/s/07oOdkOTP…

我最近寫了一篇文章,來介紹 iOS 在連線新的 Wi-Fi 網路時,如何在彈出一個 web view 以讓使用者登入或註冊之前,檢測 Captive Portals (強制網路門戶)。如果你連線過諸如酒店、酒吧或咖啡店等地的公共 Wi-Fi 網路,對這個應該會比較熟悉。如果你不熟悉 iOS 中 Captive Portals 的工作方式,可以檢視 Solving the Captive Portal Problem on iOS 這篇文章,以瞭解一些背景知識。

多年來,Apple 的 Reachability 示例程式一直被用作 App 中檢測網路訪問的基礎程式碼。搜尋 Cocoapods.org 將會看到一個很長的第三方庫列表,這些庫基本上都是基於 Reachability,並考慮了 ARC 的支援或 Swift 的相容等問題。

在 WWDC 2018 上,Apple 介紹了 iOS 12 中的一個新的框架:Network.framework,該框架包含了一個 NWPathMonitor 類。這個類為我們提供了一種監視網路狀態變化的方法,而無需包含第三方庫或 Apple 示例程式碼。

使用

只需簡單匯入 Network 框架,便可以使用 NWPathMonitor 類,如下建立一個 NWPathMonitor 例項:

let monitor = NWPathMonitor()
複製程式碼

如果你只對某個特定網路介面卡的狀態變更感興趣,例如 Wi-Fi,則可以使用 init(requiredInterfaceType:) 初始化方法,並提供 NWInterface.InterfaceType 值作為引數,來例項化 NWPathMonitor 物件,以監聽指定型別的網路介面卡,例如:

let monitor = NWPathMonitor(requiredInterfaceType: .wifi)
複製程式碼

您需要確保在某處保留對 NWPathMonitor 物件的引用(例如使用 strong 屬性),否則 ARC 可能會釋放 NWPathMonitor 物件,從而導致指定的回撥無法被呼叫。

可監控的網路型別包括:

  • cellular
  • loopback
  • other (對於虛擬或未確定的網路型別)
  • wifi
  • wiredEthernet

要獲取狀態更改的通知,需要為 pathUpdateHandler 屬性指定一個回撥,該回撥將在網路介面發生狀態更改時呼叫。例如,你的手機網路從蜂窩網路切換到 Wi-Fi 網路。然後,每當發生狀態更改時,將返回一個 NWPath 例項,可以使用該例項以確定後續的操作,如下程式碼:

monitor.pathUpdateHandler = { path in
    if path.status == .satisfied {
        print("Connected")
    }
}
複製程式碼

使用無參初始化方法與使用指定網路介面卡的初始化方法的不同點是:返回的 NWPathobject 物件的 status 屬性是否是 satisfied。例如,你只想監聽蜂窩網路,而你的手機連線的是 Wi-Fi 網路,則當 Wi-Fi 網路狀態發生變化時,並不會呼叫回撥方法,並且 path 的 status 也會保持 unsatisfied 狀態,因為手機沒有使用指定的網路連線。所以,如果你只想知道是否有網路連線,無論是 Wi-Fi 還是蜂窩,則最好使用無引數的初始化方法。

一個有趣的問題是,NWPath 在 iOS 12 中是作為 Network 框架的一部分,而實際上在 iOS 9 中就有它的身影,不過是在 NetworkExtension.framework,兩者之間有一些細微差別。

可以查詢返回的 NWPath 物件,以檢視裝置的網路介面卡的狀態資訊。另一個更有趣的屬性是 isExpensive,它標識網路介面返回的資料收費是否昂貴,如使用蜂窩資料。我們同樣可以查詢是否支援 DNS、IPv4 或 IPv6。我們可以呼叫 usesInterfaceType 方法,來檢視哪個介面改變了狀態並觸發回撥:

let isCellular: Bool = path.usesInterfaceType(.cellular)
複製程式碼

使用 NWPathMonitor 有點類似於使用其他 iOS API,例如 CLLocationManager,我們需要呼叫 start 方法以便開始接收更新,然後在完成後呼叫對應的 stop 方法。NWPathMonitor 的 start 方法要求我們為物件提供一個佇列來執行其工作:

let queue = DispatchQueue.global(qos: .background)
monitor.start(queue: queue)
複製程式碼

當我們完成監聽狀態的變化時,我們只需在呼叫 cancel() 方法。請注意,目前在 NWPathMonitor 上呼叫 cancel 後,我們無法再次啟動監聽,而是需要例項化一個新的 NWPathMonitor 例項。

請注意,如果在呼叫 start() 之前訪問 NWPathMonitor 的 currentPath 屬性,將返回 nil。實際上,如果你列印返回到更新回撥的 path,如下所示:

monitor.pathUpdateHandler = { path in
    print(path)
}
複製程式碼

則會列印以下內容:

Optional(satisfied (Path is satisfied), interface: en0, scoped, ipv4, ipv6, dns)
複製程式碼

這表明此處返回的 NWPaths 和 currentPath 屬性是可選項,儘管 API 沒有明確說明(我們可以推斷返回的 NWPath 引用是橋接到 Swift 的 Objective-C 指標)。

Captive Portals

Captive Portal 是在公共 Wi-Fi 熱點連線時顯示的網頁,通常用於在授權訪問 Internet(或訪問其他網路資源)之前強制登入、註冊或支付。在之前的一篇部落格中,我談到了從 App 開發的的角度來看,Reachability 看起來好像沒什麼問題,但實際上由於有 Captive Portals,它並不能很好完成任務。這可能導致 App 無法正常工作甚至於崩潰 -- 因為 App 可能期望從 RESTful API 中獲取一些 JSON 資料,卻從 Captive Portals 獲取到了一些 HTML。

我之前很好奇 NWPathMonitor 在檢測網路連線方面是否比 Reachability 有所改進。NWPath.Status 列舉確實提供了三種情況 -- satisfiedunsatisfiedrequiresConnection。不幸的是,Network.framework 的開發者文件並未提供這些列舉值的使用說明,而如果我們檢視 NetworkExtension.framework 文件,其中的 NWPathStatus 物件提供了 satisfiable 列舉值,裡面有一些相關文件描述:

The path is not currently satisfied, but may become satisfied upon a connection attempt. This can be due to a service, such as a VPN or a cellular data connection not being activated.

requiresConnection 列舉值似乎類似於 NWPathStatus 物件的 satisfiable 值。

好訊息是 NWPathMonitor 通常只在 captive portal 協商之後通知 path 被設定為 satisfiable 狀態,即在彈出 web view 且使用者登入後。而在沒有彈出 captive portal 的情況下,將向使用者顯示一個 Action Sheet,提供了 Use Without InternetUse Other Network 選項。如果使用者選擇了 Use Without Internet,則 NWPathMonitor 返回的 path 的狀態是 satisfied,即便實際上並沒有連網。

iOS 12+ 中檢測網路訪問

通過使用 Charles 做的一些實驗,我發現除非選擇 Use Without Internet,否則在初始化 Wi-Fi 網路連線的同時中斷連線的情況下,NWPathMonitor 沒有報告 NWPath 的 Status 被置為 statisfied。但是,如果網路連線已恢復,但隨後被刪除,則並不能檢測到這種變更,並且 path 的狀態未依然是 satisfied。如果使用者僅在火車或酒店上支付一小時的網際網路訪問費用,這種情況是可能發生的。

Connectivity

Connectivity 是一個 MIT 許可的開源框架,其目的是複用 iOS 現有的檢測 captive portal 的方法。它允許在 iOS 8+ 上使用 Reachability 準確檢測真正的 Internet 連線,這意味著在無法使用 NWPathMonitor 時,我們可以使用這個方法。並且在 iOS 12 上,Connectivity 使用了 NWPathMonitor 來提供更高的準確度。

Connectivity 已經提供了對 NWPathMonitor 的支援,可用於 iOS 12+ 系統。如果 framework 屬性設定為 network,則會使用 Network 框架來替代 SystemConfiguration 框架(Reachability),以監聽網路介面卡的狀態變更。

let connectivity = Connectivity()
connectivity.framework = .network
複製程式碼

在網路介面卡中的狀態更改後,Connectivity 會執行大量檢查以確定 Internet 訪問是否可用。另外還有一個輪詢選項,可以用來輪詢網路是否可用,即使狀態並未發生改變。可以通過設定 isPollingEnabled = true 並將 pollingInterval 設定為適當的時間值來實現這一點。

總結

Network 框架引入了一些很棒的新類,包括 NWPathMonitor,可用於在 iOS 12+ 上監聽裝置網路介面卡的狀態變化。在使用者與 captive portal 互動後會將 path 的狀態設定為 satisfied,但不會檢測後續網路訪問的丟失。Connectivity 可以為支援之前 iOS 系統的 App 提供向後相容性,並通過使用 NWPathMonitor 獲取更高的準確性。

優點

  • Apple 官方支援;
  • 無需包含第三方程式碼 - 只需匯入 iOS 12 中的 Network.framework 即可;
  • 在與 captive portal 協商後,報告 NWPath 的狀態為 satisfied;

缺點

  • 不能在 iOS 12 之前使用,這意味著如果你需要支援早期版本的 iOS,就會稍顯麻煩了;
  • 缺乏詳細文件;
  • 在初始連線成功後,不會再檢測 captive portals 以及其他 Internet 連線中斷的情況;

相關連結

關注我們

歡迎關注我們的公眾號:iOS-Tips,也歡迎加入我們的群組討論問題。可以公眾號留言 iosflutter 等關鍵詞獲取入群方式。

iOS 12+ 中檢測網路訪問

相關文章