Swift-檢查網路狀態
參考原文: https://www.appcoda.com.tw/network-framework/
檢查網路環境的方法主要是import Network之後再建立NWPathMonitor物件去偵測手機的網路環境。
因此通常會推薦寫一個NetStatus.swift的Model去統一處理。
原始碼
import Foundation
//1 匯入 Network 框架
import Network
class NetStatus: NSObject {
//MARK: - Singleton
//2 單例模式
static let shared = NetStatus()
// MARK: - init
private override init() {
super.init()
}
deinit {
stopMonitoring()
}
//MARK: - Values
//Network 框架實際上是提供一個簡單的方式,透過 NWPathMonitor 類別來觀察網路變化。
var monitor: NWPathMonitor?
// 宣告一個旗標(Flag),來標示類別是否正在觀察網路狀態的變化
// 預設值被設定為 false,因為沒有任何監控發生。
var isMonitoring = false
// callback values
//宣告一些回呼處理器(閉包),在以下情況會呼叫它們:
//類別開始監控網路變化。
var didStartMonitoringHandler: (() -> Void)?
//類別停止監控網路變化。
var didStopMonitoringHandler: (() -> Void)?
//網路狀態出現變更。
var netStatusChangeHandler: (() -> Void)?
// 確認連接狀態
var isConnected: Bool {
//當 monitor 是 nil 的時候,我們並沒有辦法判定裝置是否有連線。
guard let monitor = monitor else { return false }
//如果數值等於 satisfied,就代表裝置已連線到一個網路介面。
return monitor.currentPath.status == .satisfied
}
// 取出當前連線介面(wifi,Cellular等)
var interfaceType: NWInterface.InterfaceType? {
guard let monitor = monitor else { return nil }
return monitor.currentPath.availableInterfaces.first?.type
}
// 啟用的所有連線介面
var availableInterfacesTypes: [NWInterface.InterfaceType]? {
guard let monitor = monitor else { return nil }
return monitor.currentPath.availableInterfaces.map { $0.type }
}
/**
根據 Apple 官方文件,特定的網路介面(像是行動網路)被認為是昂貴的。
Network 框架有一個旗標讓它可以簡單地透過 currentPath 屬性確認這一點,它就叫做 isExpensive。
*/
var isExpensive: Bool {
return monitor?.currentPath.isExpensive ?? false
}
/**
ChatGPT:
NWPathMonitor.currentPath.isExpensive 是 Network API 的屬性,用來判斷目前的網路是否為昂貴網路(例如蜂窩網路或按流量計費的網路)。然而,有可能在某些情況下,即使是 5G 網路,其值也可能是 false,以下是可能的原因與解釋:
1. 運營商的配置
並非所有的蜂窩網路(包括 5G)都會被標記為「昂貴網路」。這取決於運營商的網路設置,例如某些 5G 計劃可能被視為「不限流量」,因此不會被歸類為昂貴網路。
2. iOS 的判定邏輯
iOS 的 isExpensive 是基於系統的內部判定邏輯來決定的。Apple 可能根據蜂窩網路的使用模式(如是否開啟低數據模式或計費模式)來設定這個屬性,而不僅僅是根據網路類型(如 5G)。
3. Wi-Fi 優先接入
如果設備當前同時連接到 Wi-Fi 和 5G,並且優先使用 Wi-Fi,那麼即使有 5G 連接,isExpensive 也可能是 false,因為當前的主要網路是 Wi-Fi。
4. NWPathMonitor 問題
NWPathMonitor 提供的 currentPath 可能有短暫的延遲,無法立即反映網路狀態的變化,特別是在從 Wi-Fi 切換到 5G 的過程中。
*/
var isCellular: Bool {
guard let type = interfaceType else { return false }
//如果只是單純判定是否為行動網路,只要選蜂窩(cellular)網路的技術(例如 LTE、NR 等)
//萬一要細分到是否為4G、5G就要透過CoreTelephony來做進一步分析
return type == .cellular
}
//將interfaceType轉成文字,原因在於這個值並沒有文字輸出
var typeString: String {
guard let type = interfaceType else { return "None" }
switch type {
case .cellular:
return "Cellular"
case .loopback:
return "Loopback"
case .other:
return "Other"
case .wifi:
return "WiFi"
case .wiredEthernet:
return "WiredEhernet"
default:
return "Unknown"
}
}
//MARK: - Functions
// 開始監控網路狀態變化
func startMonitoring() {
guard !isMonitoring else { return }
// 建立物件
monitor = NWPathMonitor()
/**
很重要的一點,就是觀察網路狀態變化需要在背景執行緒中執行,也不可以在主執行緒中執行。
所以,讓我們來建立一個 DispatchQueue,然後呼叫方法
*/
let queue = DispatchQueue(label: "NetStatus_Monitor")
monitor?.start(queue: queue)
/**
讓 NetStatus 在任何網路狀態變化時通知它的呼叫器,像是從無線網路切換至行動網路,或是完全斷線的時候。這個步驟是透過 pathUpdateHandler 屬性來完成的,它是一個閉包,會回傳含有關於更新網路介面、裝置有否連上網路等資訊的 NWPath 物件。不過,我們在這裡不會直接使用它,反而我們會呼叫前文宣告的 netStatusChangeHandler
*/
monitor?.pathUpdateHandler = { (path: NWPath) in
self.netStatusChangeHandler?()
}
// 最後,按需要更新 isMonitoring 旗標,並呼叫 didStartMonitoringHandler 閉包
isMonitoring = true
didStartMonitoringHandler?()
}
// 停止監控
func stopMonitoring() {
guard isMonitoring, let monitor = monitor else { return }
//要停止監控,我們只需要呼叫下列的 cancel() 方法
monitor.cancel()
//之後,也要更新我們客製化的屬性
self.monitor = nil
isMonitoring = false
//我們亦要呼叫 didStopMonitoringHandler,來通知使用這個類別的任何實體
didStopMonitoringHandler?()
}
}
使用方法
以APP使用為舉例
import UIKit
class ViewController: UIViewController {
//MARK: - @IBOutlet
@IBOutlet weak var txvInfo: UITextView!
@IBOutlet weak var btnMonitor: UIButton!
//MARK: - Values
let netStatus = NetStatus.shared
//MARK: - lifecycle
override func viewDidLoad() {
super.viewDidLoad()
txvInfo.text = ""
//事件觸發設置
netStatus.didStartMonitoringHandler = handlerStartMonitoring
netStatus.didStopMonitoringHandler = handlerStopMonitoring
netStatus.netStatusChangeHandler = handlerNetWorkChange
}
//MARK: - Functions
//Handlers事件
func handlerStartMonitoring() {
outputContent("Handler:Start Monitoring")
outputContent("NteType: \(netStatus.typeString)")
}
func handlerStopMonitoring() {
outputContent("Handler:Stop Monitoring")
}
func handlerNetWorkChange() {
outputContent("Handler:NetWork Chnage")
outputContent("NteType: \(netStatus.typeString)")//印出現在使用的網路
}
//輸出文字
func outputContent(_ str: String) {
DispatchQueue.main.async {
self.txvInfo.text.append(str + "\n")
}
}
//MARK: - @IBAction
@IBAction func doMonitoring(_ sender: Any) {
var btnTitle = "START Monitor"
if netStatus.isMonitoring {
netStatus.stopMonitoring()//開始偵測
}
else {
netStatus.startMonitoring()//停止偵測
btnTitle = "STOP Monitor"
}
btnMonitor.setTitle(btnTitle, for: .normal)//按鈕名稱更換
}
@IBAction func doCheckNet(_ sender: Any) {
//在沒有監控網路的時候,isConnected的結果一定是False
if netStatus.isMonitoring {
let showText = netStatus.isConnected ? "Connected" : "Not Connected"
outputContent(showText)
}
else {
outputContent("Monitor not start.")
}
}
}
結語
想說是參考原文之後整理出來的,但是要模仿原文寫一遍又很麻煩,所以直接把程式碼貼上去,上面的註解是我在閱讀時的筆記,應用上就沒太多註解。