123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- //
- // 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 canImport(SystemConfiguration)
- 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.
- private let mutableState = Protected(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
- }
- let weakManager = WeakManager(manager: self)
- var context = SCNetworkReachabilityContext(
- version: 0,
- info: Unmanaged.passUnretained(weakManager).toOpaque(),
- retain: { info in
- let unmanaged = Unmanaged<WeakManager>.fromOpaque(info)
- _ = unmanaged.retain()
- return UnsafeRawPointer(unmanaged.toOpaque())
- },
- release: { info in
- let unmanaged = Unmanaged<WeakManager>.fromOpaque(info)
- unmanaged.release()
- },
- copyDescription: { info in
- let unmanaged = Unmanaged<WeakManager>.fromOpaque(info)
- let weakManager = unmanaged.takeUnretainedValue()
- let description = weakManager.manager?.flags?.readableDescription ?? "nil"
- return Unmanaged.passRetained(description as CFString)
- }
- )
- let callback: SCNetworkReachabilityCallBack = { _, flags, info in
- guard let info = info else { return }
- let weakManager = Unmanaged<WeakManager>.fromOpaque(info).takeUnretainedValue()
- weakManager.manager?.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) }
- }
- }
- private final class WeakManager {
- weak var manager: NetworkReachabilityManager?
- init(manager: NetworkReachabilityManager?) {
- self.manager = manager
- }
- }
- }
- // 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) || (swift(>=5.9) && os(visionOS))
- 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
|