AutorÃa | Ultima modificación | Ver Log |
//// NetworkReachabilityManager.swift//// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)//// Permission is hereby granted, free of charge, to any person obtaining a copy// of this software and associated documentation files (the "Software"), to deal// in the Software without restriction, including without limitation the rights// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell// copies of the Software, and to permit persons to whom the Software is// furnished to do so, subject to the following conditions://// The above copyright notice and this permission notice shall be included in// all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN// THE SOFTWARE.//#if !(os(watchOS) || os(Linux) || os(Windows))import Foundationimport SystemConfiguration/// The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both cellular and/// WiFi network interfaces.////// Reachability can be used to determine background information about why a network operation failed, or to retry/// network requests when a connection is established. It should not be used to prevent a user from initiating a network/// request, as it's possible that an initial request may be required to establish reachability.open class NetworkReachabilityManager {/// Defines the various states of network reachability.public enum NetworkReachabilityStatus {/// It is unknown whether the network is reachable.case unknown/// The network is not reachable.case notReachable/// The network is reachable on the associated `ConnectionType`.case reachable(ConnectionType)init(_ flags: SCNetworkReachabilityFlags) {guard flags.isActuallyReachable else { self = .notReachable; return }var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi)if flags.isCellular { networkStatus = .reachable(.cellular) }self = networkStatus}/// Defines the various connection types detected by reachability flags.public enum ConnectionType {/// The connection type is either over Ethernet or WiFi.case ethernetOrWiFi/// The connection type is a cellular connection.case cellular}}/// A closure executed when the network reachability status changes. The closure takes a single argument: the/// network reachability status.public typealias Listener = (NetworkReachabilityStatus) -> Void/// Default `NetworkReachabilityManager` for the zero address and a `listenerQueue` of `.main`.public static let `default` = NetworkReachabilityManager()// MARK: - Properties/// Whether the network is currently reachable.open var isReachable: Bool { isReachableOnCellular || isReachableOnEthernetOrWiFi }/// Whether the network is currently reachable over the cellular interface.////// - Note: Using this property to decide whether to make a high or low bandwidth request is not recommended./// Instead, set the `allowsCellularAccess` on any `URLRequest`s being issued.///open var isReachableOnCellular: Bool { status == .reachable(.cellular) }/// Whether the network is currently reachable over Ethernet or WiFi interface.open var isReachableOnEthernetOrWiFi: Bool { status == .reachable(.ethernetOrWiFi) }/// `DispatchQueue` on which reachability will update.public let reachabilityQueue = DispatchQueue(label: "org.alamofire.reachabilityQueue")/// Flags of the current reachability type, if any.open var flags: SCNetworkReachabilityFlags? {var flags = SCNetworkReachabilityFlags()return (SCNetworkReachabilityGetFlags(reachability, &flags)) ? flags : nil}/// The current network reachability status.open var status: NetworkReachabilityStatus {flags.map(NetworkReachabilityStatus.init) ?? .unknown}/// Mutable state storage.struct MutableState {/// A closure executed when the network reachability status changes.var listener: Listener?/// `DispatchQueue` on which listeners will be called.var listenerQueue: DispatchQueue?/// Previously calculated status.var previousStatus: NetworkReachabilityStatus?}/// `SCNetworkReachability` instance providing notifications.private let reachability: SCNetworkReachability/// Protected storage for mutable state.@Protectedprivate var mutableState = MutableState()// MARK: - Initialization/// Creates an instance with the specified host.////// - Note: The `host` value must *not* contain a scheme, just the hostname.////// - Parameters:/// - host: Host used to evaluate network reachability. Must *not* include the scheme (e.g. `https`).public convenience init?(host: String) {guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil }self.init(reachability: reachability)}/// Creates an 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.public convenience init?() {var zero = sockaddr()zero.sa_len = UInt8(MemoryLayout<sockaddr>.size)zero.sa_family = sa_family_t(AF_INET)guard let reachability = SCNetworkReachabilityCreateWithAddress(nil, &zero) else { return nil }self.init(reachability: reachability)}private init(reachability: SCNetworkReachability) {self.reachability = reachability}deinit {stopListening()}// MARK: - Listening/// Starts listening for changes in network reachability status.////// - Note: Stops and removes any existing listener.////// - Parameters:/// - queue: `DispatchQueue` on which to call the `listener` closure. `.main` by default./// - listener: `Listener` closure called when reachability changes.////// - Returns: `true` if listening was started successfully, `false` otherwise.@discardableResultopen func startListening(onQueue queue: DispatchQueue = .main,onUpdatePerforming listener: @escaping Listener) -> Bool {stopListening()$mutableState.write { state instate.listenerQueue = queuestate.listener = listener}var context = SCNetworkReachabilityContext(version: 0,info: Unmanaged.passUnretained(self).toOpaque(),retain: nil,release: nil,copyDescription: nil)let callback: SCNetworkReachabilityCallBack = { _, flags, info inguard let info = info else { return }let instance = Unmanaged<NetworkReachabilityManager>.fromOpaque(info).takeUnretainedValue()instance.notifyListener(flags)}let queueAdded = SCNetworkReachabilitySetDispatchQueue(reachability, reachabilityQueue)let callbackAdded = SCNetworkReachabilitySetCallback(reachability, callback, &context)// Manually call listener to give initial state, since the framework may not.if let currentFlags = flags {reachabilityQueue.async {self.notifyListener(currentFlags)}}return callbackAdded && queueAdded}/// Stops listening for changes in network reachability status.open func stopListening() {SCNetworkReachabilitySetCallback(reachability, nil, nil)SCNetworkReachabilitySetDispatchQueue(reachability, nil)$mutableState.write { state instate.listener = nilstate.listenerQueue = nilstate.previousStatus = nil}}// MARK: - Internal - Listener Notification/// Calls the `listener` closure of the `listenerQueue` if the computed status hasn't changed.////// - Note: Should only be called from the `reachabilityQueue`.////// - Parameter flags: `SCNetworkReachabilityFlags` to use to calculate the status.func notifyListener(_ flags: SCNetworkReachabilityFlags) {let newStatus = NetworkReachabilityStatus(flags)$mutableState.write { state inguard state.previousStatus != newStatus else { return }state.previousStatus = newStatuslet listener = state.listenerstate.listenerQueue?.async { listener?(newStatus) }}}}// MARK: -extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {}extension SCNetworkReachabilityFlags {var isReachable: Bool { contains(.reachable) }var isConnectionRequired: Bool { contains(.connectionRequired) }var canConnectAutomatically: Bool { contains(.connectionOnDemand) || contains(.connectionOnTraffic) }var canConnectWithoutUserInteraction: Bool { canConnectAutomatically && !contains(.interventionRequired) }var isActuallyReachable: Bool { isReachable && (!isConnectionRequired || canConnectWithoutUserInteraction) }var isCellular: Bool {#if os(iOS) || os(tvOS)return contains(.isWWAN)#elsereturn false#endif}/// Human readable `String` for all states, to help with debugging.var readableDescription: String {let W = isCellular ? "W" : "-"let R = isReachable ? "R" : "-"let c = isConnectionRequired ? "c" : "-"let t = contains(.transientConnection) ? "t" : "-"let i = contains(.interventionRequired) ? "i" : "-"let C = contains(.connectionOnTraffic) ? "C" : "-"let D = contains(.connectionOnDemand) ? "D" : "-"let l = contains(.isLocalAddress) ? "l" : "-"let d = contains(.isDirect) ? "d" : "-"let a = contains(.connectionAutomatic) ? "a" : "-"return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)\(a)"}}#endif