Alamofire源碼解讀系列(七)之網絡監控(NetworkReachabilityManager)

Alamofire源碼解讀系列(七)之網絡監控(NetworkReachabilityManager)

本篇主要講解iOS開發中的網絡監控html

前言

在開發中,有時候咱們須要獲取這些信息:編程

  • 手機是否聯網
  • 當前網絡是WiFi仍是蜂窩

那麼我總結一下具體的使用場景有哪些?確定有遺漏:swift

  1. 聊天列表,須要實時監控當前的網絡是否是可達的,若是不可達,則出現不能聯網的提示
  2. 在線視屏播放,須要判斷當前的網絡狀態,若是不是WiFi,應該給出流量播放的提示
  3. 對於比較重要的網絡請求,在請求出錯的狀況下,判斷網路狀態,找出請求失敗緣由。
  4. 能夠把請求進行緩存後,當監聽到網絡鏈接成功後發送。舉個例子,每次進app都要把位置信息發給服務器,若是發送失敗後,發現是網絡不可達形成的失敗,那麼能夠把這個請求放入到一個隊列中,在網絡可達的時候,開啓隊列任務。
  5. 當網絡狀態變化時,實時的給用戶提示信息
  6. 獲取某個節點或地址是否是可達的

可是,極其不建議在發請求前,先檢測當前的網絡是否是可達。由於手機的網絡狀態是常常變化的》緩存

SCNetworkReachabilityFlags

SCNetworkReachabilityFlags是獲取網絡狀態最核心的東西。咱們來看看它有哪些內容:服務器

做用

SCNetworkReachabilityFlags可以判斷某個指定的網絡節點名稱或者地址是否是可達的,也能判斷該節點或地址是否是須要先創建鏈接,也能夠判斷是否是須要用戶手動去創建鏈接。網絡

注意:這裏所說的鏈接分爲用編程手段鏈接和用手動創建鏈接兩種閉包

咱們只列舉出跟本類相關的一些選項:app

  • kSCNetworkReachabilityFlagsReachable 代表當前指定的節點或地址是可達的。注意:可達不是表明節點或地址接受到了數據,而是表明數據可以離開本地,所以。就算是可達的,也不必定可以發送成功
  • kSCNetworkReachabilityFlagsConnectionRequired 代表要想和指定的節點或地址通訊,須要先創建鏈接。好比說撥號上網。注意:對於手機來講,若是沒有返回該標記,就說明手機正在使用蜂窩網路或者WiFi
  • kSCNetworkReachabilityFlagsConnectionOnTraffic 代表要想和指定的節點或地址通訊,必須先創建鏈接,可是在當前的網絡配置下,目標是可達的。注意:任何鏈接到指定的節點或地址的請求都會觸發該標記,舉個例子,在不少地方須要輸入手機,獲取驗證碼後才能聯網,就是這個原理
  • kSCNetworkReachabilityFlagsConnectionOnDemand 代表要想和指定的節點或地址通訊,必須先創建鏈接,可是在當前的網絡配置下,目標是可達的。可是創建鏈接必須經過CFSocketStream APIs才行,其餘的APIs不能創建鏈接
  • kSCNetworkReachabilityFlagsInterventionRequired 代表要想和指定的節點或地址通訊,必須先創建鏈接,可是在當前的網絡配置下,目標是可達的。須要用戶手動提供一些數據,好比密碼或者token
  • kSCNetworkReachabilityFlagsIsWWAN 代表是否是經過蜂窩網絡鏈接

上邊的這些選項,會在下邊的一個核心方法中使用到,咱們在下邊的代碼中在給出說明。async

ConnectionType

/// 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

NetworkReachabilityStatus

/// 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,第三部開啓監控,這個開啓監控的方法會在下邊講到。

Properties

在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 必不可少的對象,有了它才能獲取flags
  • previousFlags: SCNetworkReachabilityFlags 用於記錄當前的flags,在收到系統的callBack方法後,經過比較如今的flags和previousFlags來判斷是否是要調用listener函數

Initialization

關於初始化,NetworkReachabilityManager提供了三種選擇:

經過指定host

/// 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)
    }

經過init方法會默認的設置爲指向0.0.0.0

/// 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)
    }

經過指定SCNetworkReachability

private init(reachability: SCNetworkReachability) {
        self.reachability = reachability
        self.previousFlags = SCNetworkReachabilityFlags()
    }

deinit

deinit {
        stopListening()
    }

上邊的代碼代表,在NetworkReachabilityManager被銷燬的時候,會中止監控,所以在開發中就要額外注意這一點,最好讓控制器強引用它。

startListening

在開發中,對於開發某個功能,我有時候會稱爲開發某種能力類,咱們能夠採起自上而下的方法,我先定義出最基本的僞代碼,對於網絡監控咱們的僞代碼就應該是下邊這樣的:

  1. 建立一個監控者
  2. 設置監控回調事件
  3. 開始監控
  4. 中止監控

在這裏講點額外的編程技巧,上邊的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代表能夠忽略返回值。其實開始監控網絡狀態就分爲兩部:

  1. 設置Callback回調函數
  2. 設置Callback回調隊列

固然必要的前提是必須初始化了一個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))
    }

networkReachabilityStatusForFlags

這個函數是根據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) 簡書-----博客園

Alamofire源碼解讀系列(五)之結果封裝(Result) 簡書-----博客園

Alamofire源碼解讀系列(六)之Task代理(TaskDelegate) 簡書-----博客園

相關文章
相關標籤/搜索