本篇主要講解iOS開發中的網絡監控html
在開發中,有時候咱們須要獲取這些信息:編程
那麼我總結一下具體的使用場景有哪些?確定有遺漏:swift
可是,極其不建議在發請求前,先檢測當前的網絡是否是可達。由於手機的網絡狀態是常常變化的》緩存
SCNetworkReachabilityFlags是獲取網絡狀態最核心的東西。咱們來看看它有哪些內容:服務器
SCNetworkReachabilityFlags可以判斷某個指定的網絡節點名稱或者地址是否是可達的,也能判斷該節點或地址是否是須要先創建鏈接,也能夠判斷是否是須要用戶手動去創建鏈接。網絡
注意:這裏所說的鏈接分爲用編程手段鏈接和用手動創建鏈接兩種閉包
咱們只列舉出跟本類相關的一些選項:app
kSCNetworkReachabilityFlagsReachable
代表當前指定的節點或地址是可達的。注意:可達不是表明節點或地址接受到了數據,而是表明數據可以離開本地,所以。就算是可達的,也不必定可以發送成功kSCNetworkReachabilityFlagsConnectionRequired
代表要想和指定的節點或地址通訊,須要先創建鏈接。好比說撥號上網。注意:對於手機來講,若是沒有返回該標記,就說明手機正在使用蜂窩網路或者WiFikSCNetworkReachabilityFlagsConnectionOnTraffic
代表要想和指定的節點或地址通訊,必須先創建鏈接,可是在當前的網絡配置下,目標是可達的。注意:任何鏈接到指定的節點或地址的請求都會觸發該標記,舉個例子,在不少地方須要輸入手機,獲取驗證碼後才能聯網,就是這個原理kSCNetworkReachabilityFlagsConnectionOnDemand
代表要想和指定的節點或地址通訊,必須先創建鏈接,可是在當前的網絡配置下,目標是可達的。可是創建鏈接必須經過CFSocketStream APIs
才行,其餘的APIs不能創建鏈接kSCNetworkReachabilityFlagsInterventionRequired
代表要想和指定的節點或地址通訊,必須先創建鏈接,可是在當前的網絡配置下,目標是可達的。須要用戶手動提供一些數據,好比密碼或者tokenkSCNetworkReachabilityFlagsIsWWAN
代表是否是經過蜂窩網絡鏈接上邊的這些選項,會在下邊的一個核心方法中使用到,咱們在下邊的代碼中在給出說明。async
/// Defines the various connection types detected by reachability flags. /// /// - ethernetOrWiFi: The connection type is either over Ethernet or WiFi. /// - wwan: The connection type is a WWAN connection. public enum ConnectionType { case ethernetOrWiFi case wwan }
對於手機而言,咱們須要的鏈接類型就兩種,一種是蜂窩網絡,另外一種是WiFi網絡。所以在設計NetworkReachabilityManager的時候,經過上邊的枚舉獲取當前的網絡鏈接類型。ide
/// Defines the various states of network reachability. /// /// - unknown: It is unknown whether the network is reachable. /// - notReachable: The network is not reachable. /// - reachable: The network is reachable. public enum NetworkReachabilityStatus { case unknown case notReachable case reachable(ConnectionType) }
網絡狀態明顯要比網絡類型範圍更大,所以又增長了兩個選項,一個表示當前的網絡是未知的,另外一個表示當前的網路不可達。
綜上所述,咱們的目的就是拿到這個NetworkReachabilityStatus,那麼NetworkReachabilityManager是如何把NetworkReachabilityStatus傳遞出來的呢? 答案就是閉包,
/// A closure executed when the network reachability status changes. The closure takes a single argument: the /// network reachability status. public typealias Listener = (NetworkReachabilityStatus) -> Void
swift的閉包,咱們已經很熟悉了,在開發中,首先初始化NetworkReachabilityManager,而後設置Listener,第三部開啓監控,這個開啓監控的方法會在下邊講到。
在NetworkReachabilityManager中,屬性分爲public和private,咱們先看public部分:
/// Whether the network is currently reachable. public var isReachable: Bool { return isReachableOnWWAN || isReachableOnEthernetOrWiFi } /// Whether the network is currently reachable over the WWAN interface. public var isReachableOnWWAN: Bool { return networkReachabilityStatus == .reachable(.wwan) } /// Whether the network is currently reachable over Ethernet or WiFi interface. public var isReachableOnEthernetOrWiFi: Bool { return networkReachabilityStatus == .reachable(.ethernetOrWiFi) } /// The current network reachability status. public var networkReachabilityStatus: NetworkReachabilityStatus { guard let flags = self.flags else { return .unknown } return networkReachabilityStatusForFlags(flags) } /// The dispatch queue to execute the `listener` closure on. public var listenerQueue: DispatchQueue = DispatchQueue.main /// A closure executed when the network reachability status changes. public var listener: Listener?
public代表咱們能夠經過NetworkReachabilityManager實例直接得到的屬性,可以讓咱們很方便的獲取咱們想要的數據。咱們對這些屬性作一些簡單的說明:
isReachable: Bool
當前網絡是可達的,要麼是蜂窩網絡,要麼是WiFi鏈接isReachableOnWWAN: Bool
代表當前網絡是經過蜂窩網絡鏈接isReachableOnEthernetOrWiFi: Bool
代表當前網絡是經過WiFi鏈接networkReachabilityStatus: NetworkReachabilityStatus
返回當前的網絡狀態,這也是上邊3個判斷的基礎listenerQueue
監聽listener在那個隊列中調用,默認的是主隊列listener: Listener
監聽閉包,當網絡狀態發生變化時會調用上邊這些public屬性有的是隻讀的,有的不是,咱們在看看private屬性:
flags: SCNetworkReachabilityFlags?
主要目的是獲取flags,在上邊咱們介紹過,網絡狀態就是根據flags判斷出來的是經過下邊的方法獲取到的:
@available(iOS 2.0, *) public func SCNetworkReachabilityGetFlags(_ target: SCNetworkReachability, _ flags: UnsafeMutablePointer<SCNetworkReachabilityFlags>) -> Bool
reachability: SCNetworkReachability
必不可少的對象,有了它才能獲取flagspreviousFlags: SCNetworkReachabilityFlags
用於記錄當前的flags,在收到系統的callBack方法後,經過比較如今的flags和previousFlags來判斷是否是要調用listener函數
關於初始化,NetworkReachabilityManager提供了三種選擇:
/// Creates a `NetworkReachabilityManager` instance with the specified host. /// /// - parameter host: The host used to evaluate network reachability. /// /// - returns: The new `NetworkReachabilityManager` instance. public convenience init?(host: String) { guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil } self.init(reachability: reachability) }
/// Creates a `NetworkReachabilityManager` instance that monitors the address 0.0.0.0. /// /// Reachability treats the 0.0.0.0 address as a special token that causes it to monitor the general routing /// status of the device, both IPv4 and IPv6. /// /// - returns: The new `NetworkReachabilityManager` instance. public convenience init?() { var address = sockaddr_in() address.sin_len = UInt8(MemoryLayout<sockaddr_in>.size) address.sin_family = sa_family_t(AF_INET) guard let reachability = withUnsafePointer(to: &address, { pointer in return pointer.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout<sockaddr>.size) { return SCNetworkReachabilityCreateWithAddress(nil, $0) } }) else { return nil } self.init(reachability: reachability) }
private init(reachability: SCNetworkReachability) { self.reachability = reachability self.previousFlags = SCNetworkReachabilityFlags() }
deinit { stopListening() }
上邊的代碼代表,在NetworkReachabilityManager被銷燬的時候,會中止監控,所以在開發中就要額外注意這一點,最好讓控制器強引用它。
在開發中,對於開發某個功能,我有時候會稱爲開發某種能力類,咱們能夠採起自上而下的方法,我先定義出最基本的僞代碼,對於網絡監控咱們的僞代碼就應該是下邊這樣的:
在這裏講點額外的編程技巧,上邊的4個僞代碼咱們能夠成爲子程序,每一個子程序都應該有必定的內聚性要求,就是說每一個子程序最好可以實現一個單一的功能。子程序會出現成對出現的狀況,好比開始和中止,等等。那麼咱們如今要講的就是第三步,開始監控。
@discardableResult public func startListening() -> Bool { var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil) context.info = Unmanaged.passUnretained(self).toOpaque() let callbackEnabled = SCNetworkReachabilitySetCallback( reachability, { (_, flags, info) in let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!).takeUnretainedValue() reachability.notifyListener(flags) }, &context ) let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue) listenerQueue.async { self.previousFlags = SCNetworkReachabilityFlags() self.notifyListener(self.flags ?? SCNetworkReachabilityFlags()) } return callbackEnabled && queueEnabled }
@discardableResult代表能夠忽略返回值。其實開始監控網絡狀態就分爲兩部:
固然必要的前提是必須初始化了一個reachability。
這裏有一些頗有意思的東西,可能咱們在swift中是不常見的。好比:Unmanaged.passUnretained(self).toOpaque()
,好比:let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!).takeUnretainedValue()
/// A type for propagating an unmanaged object reference. /// /// When you use this type, you become partially responsible for /// keeping the object alive. public struct Unmanaged<Instance : AnyObject> { /// Unsafely turns an opaque C pointer into an unmanaged class reference. /// /// This operation does not change reference counts. /// /// let str: CFString = Unmanaged.fromOpaque(ptr).takeUnretainedValue() /// /// - Parameter value: An opaque C pointer. /// - Returns: An unmanaged class reference to `value`. public static func fromOpaque(_ value: UnsafeRawPointer) -> Unmanaged<Instance> /// Unsafely converts an unmanaged class reference to a pointer. /// /// This operation does not change reference counts. /// /// let str0: CFString = "boxcar" /// let bits = Unmanaged.passUnretained(str0) /// let ptr = bits.toOpaque() /// /// - Returns: An opaque pointer to the value of this unmanaged reference. public func toOpaque() -> UnsafeMutableRawPointer /// Creates an unmanaged reference with an unbalanced retain. /// /// The instance passed as `value` will leak if nothing eventually balances /// the retain. /// /// This is useful when passing an object to an API which Swift does not know /// the ownership rules for, but you know that the API expects you to pass /// the object at +1. /// /// - Parameter value: A class instance. /// - Returns: An unmanaged reference to the object passed as `value`. public static func passRetained(_ value: Instance) -> Unmanaged<Instance> /// Creates an unmanaged reference without performing an unbalanced /// retain. /// /// This is useful when passing a reference to an API which Swift /// does not know the ownership rules for, but you know that the /// API expects you to pass the object at +0. /// /// CFArraySetValueAtIndex(.passUnretained(array), i, /// .passUnretained(object)) /// /// - Parameter value: A class instance. /// - Returns: An unmanaged reference to the object passed as `value`. public static func passUnretained(_ value: Instance) -> Unmanaged<Instance> /// Gets the value of this unmanaged reference as a managed /// reference without consuming an unbalanced retain of it. /// /// This is useful when a function returns an unmanaged reference /// and you know that you're not responsible for releasing the result. /// /// - Returns: The object referenced by this `Unmanaged` instance. public func takeUnretainedValue() -> Instance /// Gets the value of this unmanaged reference as a managed /// reference and consumes an unbalanced retain of it. /// /// This is useful when a function returns an unmanaged reference /// and you know that you're responsible for releasing the result. /// /// - Returns: The object referenced by this `Unmanaged` instance. public func takeRetainedValue() -> Instance /// Performs an unbalanced retain of the object. public func retain() -> Unmanaged<Instance> /// Performs an unbalanced release of the object. public func release() /// Performs an unbalanced autorelease of the object. public func autorelease() -> Unmanaged<Instance> }
這裏提供一個文章地址[HandyJSON] 設計思路簡析,關於swift中指針的使用能夠參考這篇文章。很強大啊。後續我會寫HandyJson
的源碼解讀文章。
在上邊的開始監控中有一個函數:notifyListener
,這個函數的目的就是通知監聽者,也就是觸發回調函數。
func notifyListener(_ flags: SCNetworkReachabilityFlags) { guard previousFlags != flags else { return } previousFlags = flags listener?(networkReachabilityStatusForFlags(flags)) }
這個函數是根據flags獲取狀態的核心函數,可是我以爲沒什麼好說的,在開發中用的也很少,咱們把代碼粘一下,而後重點來講說swift中運算符==
重載:
func networkReachabilityStatusForFlags(_ flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus { /// 這裏的contains函數要傳遞的值是OptionSet自身,所以.reachable換成SCNetworkReachabilityFlags.reachable也是能夠的,reachable是一個靜態方法 /// flags.contains(.reachable)若是是true,就表明有網絡鏈接 guard flags.contains(.reachable) else { return .notReachable } var networkStatus: NetworkReachabilityStatus = .notReachable if !flags.contains(.connectionRequired) { networkStatus = .reachable(.ethernetOrWiFi) } if flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic) { if !flags.contains(.interventionRequired) { networkStatus = .reachable(.ethernetOrWiFi) } } #if os(iOS) if flags.contains(.isWWAN) { networkStatus = .reachable(.wwan) } #endif return networkStatus }
要想重載==
,須要實現Equatable
協議:
public protocol Equatable { /// Returns a Boolean value indicating whether two values are equal. /// /// Equality is the inverse of inequality. For any values `a` and `b`, /// `a == b` implies that `a != b` is `false`. /// /// - Parameters: /// - lhs: A value to compare. /// - rhs: Another value to compare. public static func ==(lhs: Self, rhs: Self) -> Bool }
其實,這種思想仍是很重要的,在開發中能夠經過這種方式來判斷兩個模型是否是相同,等等不少種使用場景。我簡單的把Apple文檔中的註釋說明部分翻譯一下。
==
和!=
是對立統一的關係,咱們自定義了==
,同理,!=
也就支持了。在swift中,不少基本的數據類型都支持了Equatable
協議。
Equatable
協議的一個典型的應用場景就是判斷一個集合中是否包含某個值。在swift中,若是集合中的值都實現了Equatable
協議,那麼就能夠經過contains(_:)
方法來判斷是否是包含該值。這也說明了contains(_:)
內部實現應該是經過==
來實現的。使用contains(_:)
方法的好處就是省去了咱們遍歷數據,而後再進行判斷的繁瑣步驟。咱們看個例子:
/// let students = ["Nora", "Fern", "Ryan", "Rainer"] /// /// let nameToCheck = "Ryan" /// if students.contains(nameToCheck) { /// print("\(nameToCheck) is signed up!") /// } else { /// print("No record of \(nameToCheck).") /// } /// // Prints "Ryan is signed up!"
須要把==
聲明成爲自定義類型的靜態方法
假如說咱們有一個街道地址的結構體:
/// struct StreetAddress { /// let number: String /// let street: String /// let unit: String? /// /// init(_ number: String, _ street: String, unit: String? = nil) { /// self.number = number /// self.street = street /// self.unit = unit /// } /// }
咱們讓StreetAddress
實現Equatable
協議:
/// /// extension StreetAddress: Equatable { /// static func == (lhs: StreetAddress, rhs: StreetAddress) -> Bool { /// return /// lhs.number == rhs.number && /// lhs.street == rhs.street && /// lhs.unit == rhs.unit /// } /// } ///
接下來咱們就能使用系統的contains(_:)
方法來判斷一個集合中是否是包含摸個街道地址了。
/// /// let addresses = [StreetAddress("1490", "Grove Street"), /// StreetAddress("2119", "Maple Avenue"), /// StreetAddress("1400", "16th Street")] /// let home = StreetAddress("1400", "16th Street") /// /// print(addresses[0] == home) /// // Prints "false" /// print(addresses.contains(home)) /// // Prints "true" ///
有了上邊的知識,咱們在看看NetworkReachabilityManager
是怎麼用的:
extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {} /// Returns whether the two network reachability status values are equal. /// /// - parameter lhs: The left-hand side value to compare. /// - parameter rhs: The right-hand side value to compare. /// /// - returns: `true` if the two values are equal, `false` otherwise. public func ==( lhs: NetworkReachabilityManager.NetworkReachabilityStatus, rhs: NetworkReachabilityManager.NetworkReachabilityStatus) -> Bool { switch (lhs, rhs) { case (.unknown, .unknown): return true case (.notReachable, .notReachable): return true case let (.reachable(lhsConnectionType), .reachable(rhsConnectionType)): return lhsConnectionType == rhsConnectionType default: return false } }
在swift中,static函數還能夠像上邊這麼用,把函數寫到類的代碼塊以外,固然,上邊的代碼也能夠這麼寫:
extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable { public static func ==( lhs: NetworkReachabilityManager.NetworkReachabilityStatus, rhs: NetworkReachabilityManager.NetworkReachabilityStatus) -> Bool { switch (lhs, rhs) { case (.unknown, .unknown): return true case (.notReachable, .notReachable): return true case let (.reachable(lhsConnectionType), .reachable(rhsConnectionType)): return lhsConnectionType == rhsConnectionType default: return false } } }
因爲知識水平有限,若有錯誤,還望指出
Alamofire源碼解讀系列(一)之概述和使用 簡書-----博客園
Alamofire源碼解讀系列(二)之錯誤處理(AFError) 簡書-----博客園
Alamofire源碼解讀系列(三)之通知處理(Notification) 簡書-----博客園
Alamofire源碼解讀系列(四)之參數編碼(ParameterEncoding) 簡書-----博客園