NetworkReachabilityManager.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. #if !(os(watchOS) || os(Linux) || os(Windows))
  2. import Foundation
  3. import SystemConfiguration
  4. /// The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both cellular and
  5. /// WiFi network interfaces.
  6. ///
  7. /// Reachability can be used to determine background information about why a network operation failed, or to retry
  8. /// network requests when a connection is established. It should not be used to prevent a user from initiating a network
  9. /// request, as it's possible that an initial request may be required to establish reachability.
  10. open class NetworkReachabilityManager {
  11. /// Defines the various states of network reachability.
  12. public enum NetworkReachabilityStatus {
  13. /// It is unknown whether the network is reachable.
  14. case unknown
  15. /// The network is not reachable.
  16. case notReachable
  17. /// The network is reachable on the associated `ConnectionType`.
  18. case reachable(ConnectionType)
  19. init(_ flags: SCNetworkReachabilityFlags) {
  20. guard flags.isActuallyReachable else { self = .notReachable
  21. return }
  22. var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi)
  23. if flags.isCellular { networkStatus = .reachable(.cellular) }
  24. self = networkStatus
  25. }
  26. /// Defines the various connection types detected by reachability flags.
  27. public enum ConnectionType {
  28. /// The connection type is either over Ethernet or WiFi.
  29. case ethernetOrWiFi
  30. /// The connection type is a cellular connection.
  31. case cellular
  32. }
  33. }
  34. /// A closure executed when the network reachability status changes. The closure takes a single argument: the
  35. /// network reachability status.
  36. public typealias Listener = (NetworkReachabilityStatus) -> Void
  37. /// Default `NetworkReachabilityManager` for the zero address and a `listenerQueue` of `.main`.
  38. public static let `default` = NetworkReachabilityManager()
  39. // MARK: - Properties
  40. /// Whether the network is currently reachable.
  41. open var isReachable: Bool { isReachableOnCellular || isReachableOnEthernetOrWiFi }
  42. /// Whether the network is currently reachable over the cellular interface.
  43. ///
  44. /// - Note: Using this property to decide whether to make a high or low bandwidth request is not recommended.
  45. /// Instead, set the `allowsCellularAccess` on any `URLRequest`s being issued.
  46. ///
  47. open var isReachableOnCellular: Bool { status == .reachable(.cellular) }
  48. /// Whether the network is currently reachable over Ethernet or WiFi interface.
  49. open var isReachableOnEthernetOrWiFi: Bool { status == .reachable(.ethernetOrWiFi) }
  50. /// `DispatchQueue` on which reachability will update.
  51. public let reachabilityQueue = DispatchQueue(label: "org.alamofire.reachabilityQueue")
  52. /// Flags of the current reachability type, if any.
  53. open var flags: SCNetworkReachabilityFlags? {
  54. var flags = SCNetworkReachabilityFlags()
  55. return (SCNetworkReachabilityGetFlags(reachability, &flags)) ? flags : nil
  56. }
  57. /// The current network reachability status.
  58. open var status: NetworkReachabilityStatus {
  59. flags.map(NetworkReachabilityStatus.init) ?? .unknown
  60. }
  61. /// Mutable state storage.
  62. struct MutableState {
  63. /// A closure executed when the network reachability status changes.
  64. var listener: Listener?
  65. /// `DispatchQueue` on which listeners will be called.
  66. var listenerQueue: DispatchQueue?
  67. /// Previously calculated status.
  68. var previousStatus: NetworkReachabilityStatus?
  69. }
  70. /// `SCNetworkReachability` instance providing notifications.
  71. private let reachability: SCNetworkReachability
  72. /// Protected storage for mutable state.
  73. @Protected private var mutableState = MutableState()
  74. // MARK: - Initialization
  75. /// Creates an instance with the specified host.
  76. ///
  77. /// - Note: The `host` value must *not* contain a scheme, just the hostname.
  78. ///
  79. /// - Parameters:
  80. /// - host: Host used to evaluate network reachability. Must *not* include the scheme (e.g. `https`).
  81. public convenience init?(host: String) {
  82. guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil }
  83. self.init(reachability: reachability)
  84. }
  85. /// Creates an instance that monitors the address 0.0.0.0.
  86. ///
  87. /// Reachability treats the 0.0.0.0 address as a special token that causes it to monitor the general routing
  88. /// status of the device, both IPv4 and IPv6.
  89. public convenience init?() {
  90. var zero = sockaddr()
  91. zero.sa_len = UInt8(MemoryLayout<sockaddr>.size)
  92. zero.sa_family = sa_family_t(AF_INET)
  93. guard let reachability = SCNetworkReachabilityCreateWithAddress(nil, &zero) else { return nil }
  94. self.init(reachability: reachability)
  95. }
  96. private init(reachability: SCNetworkReachability) {
  97. self.reachability = reachability
  98. }
  99. deinit {
  100. stopListening()
  101. }
  102. // MARK: - Listening
  103. /// Starts listening for changes in network reachability status.
  104. ///
  105. /// - Note: Stops and removes any existing listener.
  106. ///
  107. /// - Parameters:
  108. /// - queue: `DispatchQueue` on which to call the `listener` closure. `.main` by default.
  109. /// - listener: `Listener` closure called when reachability changes.
  110. ///
  111. /// - Returns: `true` if listening was started successfully, `false` otherwise.
  112. @discardableResult open func startListening(
  113. onQueue queue: DispatchQueue = .main,
  114. onUpdatePerforming listener: @escaping Listener
  115. ) -> Bool {
  116. stopListening()
  117. $mutableState.write { state in
  118. state.listenerQueue = queue
  119. state.listener = listener
  120. }
  121. var context = SCNetworkReachabilityContext(
  122. version: 0,
  123. info: Unmanaged.passUnretained(self).toOpaque(),
  124. retain: nil,
  125. release: nil,
  126. copyDescription: nil
  127. )
  128. let callback: SCNetworkReachabilityCallBack = { _, flags, info in
  129. guard let info = info else { return }
  130. let instance = Unmanaged<NetworkReachabilityManager>.fromOpaque(info).takeUnretainedValue()
  131. instance.notifyListener(flags)
  132. }
  133. let queueAdded = SCNetworkReachabilitySetDispatchQueue(reachability, reachabilityQueue)
  134. let callbackAdded = SCNetworkReachabilitySetCallback(reachability, callback, &context)
  135. // Manually call listener to give initial state, since the framework may not.
  136. if let currentFlags = flags {
  137. reachabilityQueue.async {
  138. self.notifyListener(currentFlags)
  139. }
  140. }
  141. return callbackAdded && queueAdded
  142. }
  143. /// Stops listening for changes in network reachability status.
  144. open func stopListening() {
  145. SCNetworkReachabilitySetCallback(reachability, nil, nil)
  146. SCNetworkReachabilitySetDispatchQueue(reachability, nil)
  147. $mutableState.write { state in
  148. state.listener = nil
  149. state.listenerQueue = nil
  150. state.previousStatus = nil
  151. }
  152. }
  153. // MARK: - Internal - Listener Notification
  154. /// Calls the `listener` closure of the `listenerQueue` if the computed status hasn't changed.
  155. ///
  156. /// - Note: Should only be called from the `reachabilityQueue`.
  157. ///
  158. /// - Parameter flags: `SCNetworkReachabilityFlags` to use to calculate the status.
  159. func notifyListener(_ flags: SCNetworkReachabilityFlags) {
  160. let newStatus = NetworkReachabilityStatus(flags)
  161. $mutableState.write { state in
  162. guard state.previousStatus != newStatus else { return }
  163. state.previousStatus = newStatus
  164. let listener = state.listener
  165. state.listenerQueue?.async { listener?(newStatus) }
  166. }
  167. }
  168. }
  169. // MARK: -
  170. extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {}
  171. extension SCNetworkReachabilityFlags {
  172. var isReachable: Bool { contains(.reachable) }
  173. var isConnectionRequired: Bool { contains(.connectionRequired) }
  174. var canConnectAutomatically: Bool { contains(.connectionOnDemand) || contains(.connectionOnTraffic) }
  175. var canConnectWithoutUserInteraction: Bool { canConnectAutomatically && !contains(.interventionRequired) }
  176. var isActuallyReachable: Bool { isReachable && (!isConnectionRequired || canConnectWithoutUserInteraction) }
  177. var isCellular: Bool {
  178. #if os(iOS) || os(tvOS)
  179. return contains(.isWWAN)
  180. #else
  181. return false
  182. #endif
  183. }
  184. /// Human readable `String` for all states, to help with debugging.
  185. var readableDescription: String {
  186. let W = isCellular ? "W" : "-"
  187. let R = isReachable ? "R" : "-"
  188. let c = isConnectionRequired ? "c" : "-"
  189. let t = contains(.transientConnection) ? "t" : "-"
  190. let i = contains(.interventionRequired) ? "i" : "-"
  191. let C = contains(.connectionOnTraffic) ? "C" : "-"
  192. let D = contains(.connectionOnDemand) ? "D" : "-"
  193. let l = contains(.isLocalAddress) ? "l" : "-"
  194. let d = contains(.isDirect) ? "d" : "-"
  195. let a = contains(.connectionAutomatic) ? "a" : "-"
  196. return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)\(a)"
  197. }
  198. }
  199. #endif