You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
weibo/Pods/Alamofire/Source/NetworkReachabilityManager....

268 lines
11 KiB

4 years ago
//
// 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))
import Foundation
import 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.
@Protected
private 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.
@discardableResult
open func startListening(onQueue queue: DispatchQueue = .main,
onUpdatePerforming listener: @escaping Listener) -> Bool {
stopListening()
$mutableState.write { state in
state.listenerQueue = queue
state.listener = listener
}
var context = SCNetworkReachabilityContext(version: 0,
info: Unmanaged.passUnretained(self).toOpaque(),
retain: nil,
release: nil,
copyDescription: nil)
let callback: SCNetworkReachabilityCallBack = { _, flags, info in
guard 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 in
state.listener = nil
state.listenerQueue = nil
state.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 in
guard state.previousStatus != newStatus else { return }
state.previousStatus = newStatus
let listener = state.listener
state.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)
#else
return 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