1 |
efrain |
1 |
//
|
|
|
2 |
// NetworkReachabilityManager.swift
|
|
|
3 |
//
|
|
|
4 |
// Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
|
|
|
5 |
//
|
|
|
6 |
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
7 |
// of this software and associated documentation files (the "Software"), to deal
|
|
|
8 |
// in the Software without restriction, including without limitation the rights
|
|
|
9 |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
10 |
// copies of the Software, and to permit persons to whom the Software is
|
|
|
11 |
// furnished to do so, subject to the following conditions:
|
|
|
12 |
//
|
|
|
13 |
// The above copyright notice and this permission notice shall be included in
|
|
|
14 |
// all copies or substantial portions of the Software.
|
|
|
15 |
//
|
|
|
16 |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
17 |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
18 |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
19 |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
20 |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
21 |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
22 |
// THE SOFTWARE.
|
|
|
23 |
//
|
|
|
24 |
|
|
|
25 |
#if !(os(watchOS) || os(Linux) || os(Windows))
|
|
|
26 |
|
|
|
27 |
import Foundation
|
|
|
28 |
import SystemConfiguration
|
|
|
29 |
|
|
|
30 |
/// The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both cellular and
|
|
|
31 |
/// WiFi network interfaces.
|
|
|
32 |
///
|
|
|
33 |
/// Reachability can be used to determine background information about why a network operation failed, or to retry
|
|
|
34 |
/// network requests when a connection is established. It should not be used to prevent a user from initiating a network
|
|
|
35 |
/// request, as it's possible that an initial request may be required to establish reachability.
|
|
|
36 |
open class NetworkReachabilityManager {
|
|
|
37 |
/// Defines the various states of network reachability.
|
|
|
38 |
public enum NetworkReachabilityStatus {
|
|
|
39 |
/// It is unknown whether the network is reachable.
|
|
|
40 |
case unknown
|
|
|
41 |
/// The network is not reachable.
|
|
|
42 |
case notReachable
|
|
|
43 |
/// The network is reachable on the associated `ConnectionType`.
|
|
|
44 |
case reachable(ConnectionType)
|
|
|
45 |
|
|
|
46 |
init(_ flags: SCNetworkReachabilityFlags) {
|
|
|
47 |
guard flags.isActuallyReachable else { self = .notReachable; return }
|
|
|
48 |
|
|
|
49 |
var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi)
|
|
|
50 |
|
|
|
51 |
if flags.isCellular { networkStatus = .reachable(.cellular) }
|
|
|
52 |
|
|
|
53 |
self = networkStatus
|
|
|
54 |
}
|
|
|
55 |
|
|
|
56 |
/// Defines the various connection types detected by reachability flags.
|
|
|
57 |
public enum ConnectionType {
|
|
|
58 |
/// The connection type is either over Ethernet or WiFi.
|
|
|
59 |
case ethernetOrWiFi
|
|
|
60 |
/// The connection type is a cellular connection.
|
|
|
61 |
case cellular
|
|
|
62 |
}
|
|
|
63 |
}
|
|
|
64 |
|
|
|
65 |
/// A closure executed when the network reachability status changes. The closure takes a single argument: the
|
|
|
66 |
/// network reachability status.
|
|
|
67 |
public typealias Listener = (NetworkReachabilityStatus) -> Void
|
|
|
68 |
|
|
|
69 |
/// Default `NetworkReachabilityManager` for the zero address and a `listenerQueue` of `.main`.
|
|
|
70 |
public static let `default` = NetworkReachabilityManager()
|
|
|
71 |
|
|
|
72 |
// MARK: - Properties
|
|
|
73 |
|
|
|
74 |
/// Whether the network is currently reachable.
|
|
|
75 |
open var isReachable: Bool { isReachableOnCellular || isReachableOnEthernetOrWiFi }
|
|
|
76 |
|
|
|
77 |
/// Whether the network is currently reachable over the cellular interface.
|
|
|
78 |
///
|
|
|
79 |
/// - Note: Using this property to decide whether to make a high or low bandwidth request is not recommended.
|
|
|
80 |
/// Instead, set the `allowsCellularAccess` on any `URLRequest`s being issued.
|
|
|
81 |
///
|
|
|
82 |
open var isReachableOnCellular: Bool { status == .reachable(.cellular) }
|
|
|
83 |
|
|
|
84 |
/// Whether the network is currently reachable over Ethernet or WiFi interface.
|
|
|
85 |
open var isReachableOnEthernetOrWiFi: Bool { status == .reachable(.ethernetOrWiFi) }
|
|
|
86 |
|
|
|
87 |
/// `DispatchQueue` on which reachability will update.
|
|
|
88 |
public let reachabilityQueue = DispatchQueue(label: "org.alamofire.reachabilityQueue")
|
|
|
89 |
|
|
|
90 |
/// Flags of the current reachability type, if any.
|
|
|
91 |
open var flags: SCNetworkReachabilityFlags? {
|
|
|
92 |
var flags = SCNetworkReachabilityFlags()
|
|
|
93 |
|
|
|
94 |
return (SCNetworkReachabilityGetFlags(reachability, &flags)) ? flags : nil
|
|
|
95 |
}
|
|
|
96 |
|
|
|
97 |
/// The current network reachability status.
|
|
|
98 |
open var status: NetworkReachabilityStatus {
|
|
|
99 |
flags.map(NetworkReachabilityStatus.init) ?? .unknown
|
|
|
100 |
}
|
|
|
101 |
|
|
|
102 |
/// Mutable state storage.
|
|
|
103 |
struct MutableState {
|
|
|
104 |
/// A closure executed when the network reachability status changes.
|
|
|
105 |
var listener: Listener?
|
|
|
106 |
/// `DispatchQueue` on which listeners will be called.
|
|
|
107 |
var listenerQueue: DispatchQueue?
|
|
|
108 |
/// Previously calculated status.
|
|
|
109 |
var previousStatus: NetworkReachabilityStatus?
|
|
|
110 |
}
|
|
|
111 |
|
|
|
112 |
/// `SCNetworkReachability` instance providing notifications.
|
|
|
113 |
private let reachability: SCNetworkReachability
|
|
|
114 |
|
|
|
115 |
/// Protected storage for mutable state.
|
|
|
116 |
@Protected
|
|
|
117 |
private var mutableState = MutableState()
|
|
|
118 |
|
|
|
119 |
// MARK: - Initialization
|
|
|
120 |
|
|
|
121 |
/// Creates an instance with the specified host.
|
|
|
122 |
///
|
|
|
123 |
/// - Note: The `host` value must *not* contain a scheme, just the hostname.
|
|
|
124 |
///
|
|
|
125 |
/// - Parameters:
|
|
|
126 |
/// - host: Host used to evaluate network reachability. Must *not* include the scheme (e.g. `https`).
|
|
|
127 |
public convenience init?(host: String) {
|
|
|
128 |
guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil }
|
|
|
129 |
|
|
|
130 |
self.init(reachability: reachability)
|
|
|
131 |
}
|
|
|
132 |
|
|
|
133 |
/// Creates an instance that monitors the address 0.0.0.0.
|
|
|
134 |
///
|
|
|
135 |
/// Reachability treats the 0.0.0.0 address as a special token that causes it to monitor the general routing
|
|
|
136 |
/// status of the device, both IPv4 and IPv6.
|
|
|
137 |
public convenience init?() {
|
|
|
138 |
var zero = sockaddr()
|
|
|
139 |
zero.sa_len = UInt8(MemoryLayout<sockaddr>.size)
|
|
|
140 |
zero.sa_family = sa_family_t(AF_INET)
|
|
|
141 |
|
|
|
142 |
guard let reachability = SCNetworkReachabilityCreateWithAddress(nil, &zero) else { return nil }
|
|
|
143 |
|
|
|
144 |
self.init(reachability: reachability)
|
|
|
145 |
}
|
|
|
146 |
|
|
|
147 |
private init(reachability: SCNetworkReachability) {
|
|
|
148 |
self.reachability = reachability
|
|
|
149 |
}
|
|
|
150 |
|
|
|
151 |
deinit {
|
|
|
152 |
stopListening()
|
|
|
153 |
}
|
|
|
154 |
|
|
|
155 |
// MARK: - Listening
|
|
|
156 |
|
|
|
157 |
/// Starts listening for changes in network reachability status.
|
|
|
158 |
///
|
|
|
159 |
/// - Note: Stops and removes any existing listener.
|
|
|
160 |
///
|
|
|
161 |
/// - Parameters:
|
|
|
162 |
/// - queue: `DispatchQueue` on which to call the `listener` closure. `.main` by default.
|
|
|
163 |
/// - listener: `Listener` closure called when reachability changes.
|
|
|
164 |
///
|
|
|
165 |
/// - Returns: `true` if listening was started successfully, `false` otherwise.
|
|
|
166 |
@discardableResult
|
|
|
167 |
open func startListening(onQueue queue: DispatchQueue = .main,
|
|
|
168 |
onUpdatePerforming listener: @escaping Listener) -> Bool {
|
|
|
169 |
stopListening()
|
|
|
170 |
|
|
|
171 |
$mutableState.write { state in
|
|
|
172 |
state.listenerQueue = queue
|
|
|
173 |
state.listener = listener
|
|
|
174 |
}
|
|
|
175 |
|
|
|
176 |
var context = SCNetworkReachabilityContext(version: 0,
|
|
|
177 |
info: Unmanaged.passUnretained(self).toOpaque(),
|
|
|
178 |
retain: nil,
|
|
|
179 |
release: nil,
|
|
|
180 |
copyDescription: nil)
|
|
|
181 |
let callback: SCNetworkReachabilityCallBack = { _, flags, info in
|
|
|
182 |
guard let info = info else { return }
|
|
|
183 |
|
|
|
184 |
let instance = Unmanaged<NetworkReachabilityManager>.fromOpaque(info).takeUnretainedValue()
|
|
|
185 |
instance.notifyListener(flags)
|
|
|
186 |
}
|
|
|
187 |
|
|
|
188 |
let queueAdded = SCNetworkReachabilitySetDispatchQueue(reachability, reachabilityQueue)
|
|
|
189 |
let callbackAdded = SCNetworkReachabilitySetCallback(reachability, callback, &context)
|
|
|
190 |
|
|
|
191 |
// Manually call listener to give initial state, since the framework may not.
|
|
|
192 |
if let currentFlags = flags {
|
|
|
193 |
reachabilityQueue.async {
|
|
|
194 |
self.notifyListener(currentFlags)
|
|
|
195 |
}
|
|
|
196 |
}
|
|
|
197 |
|
|
|
198 |
return callbackAdded && queueAdded
|
|
|
199 |
}
|
|
|
200 |
|
|
|
201 |
/// Stops listening for changes in network reachability status.
|
|
|
202 |
open func stopListening() {
|
|
|
203 |
SCNetworkReachabilitySetCallback(reachability, nil, nil)
|
|
|
204 |
SCNetworkReachabilitySetDispatchQueue(reachability, nil)
|
|
|
205 |
$mutableState.write { state in
|
|
|
206 |
state.listener = nil
|
|
|
207 |
state.listenerQueue = nil
|
|
|
208 |
state.previousStatus = nil
|
|
|
209 |
}
|
|
|
210 |
}
|
|
|
211 |
|
|
|
212 |
// MARK: - Internal - Listener Notification
|
|
|
213 |
|
|
|
214 |
/// Calls the `listener` closure of the `listenerQueue` if the computed status hasn't changed.
|
|
|
215 |
///
|
|
|
216 |
/// - Note: Should only be called from the `reachabilityQueue`.
|
|
|
217 |
///
|
|
|
218 |
/// - Parameter flags: `SCNetworkReachabilityFlags` to use to calculate the status.
|
|
|
219 |
func notifyListener(_ flags: SCNetworkReachabilityFlags) {
|
|
|
220 |
let newStatus = NetworkReachabilityStatus(flags)
|
|
|
221 |
|
|
|
222 |
$mutableState.write { state in
|
|
|
223 |
guard state.previousStatus != newStatus else { return }
|
|
|
224 |
|
|
|
225 |
state.previousStatus = newStatus
|
|
|
226 |
|
|
|
227 |
let listener = state.listener
|
|
|
228 |
state.listenerQueue?.async { listener?(newStatus) }
|
|
|
229 |
}
|
|
|
230 |
}
|
|
|
231 |
}
|
|
|
232 |
|
|
|
233 |
// MARK: -
|
|
|
234 |
|
|
|
235 |
extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {}
|
|
|
236 |
|
|
|
237 |
extension SCNetworkReachabilityFlags {
|
|
|
238 |
var isReachable: Bool { contains(.reachable) }
|
|
|
239 |
var isConnectionRequired: Bool { contains(.connectionRequired) }
|
|
|
240 |
var canConnectAutomatically: Bool { contains(.connectionOnDemand) || contains(.connectionOnTraffic) }
|
|
|
241 |
var canConnectWithoutUserInteraction: Bool { canConnectAutomatically && !contains(.interventionRequired) }
|
|
|
242 |
var isActuallyReachable: Bool { isReachable && (!isConnectionRequired || canConnectWithoutUserInteraction) }
|
|
|
243 |
var isCellular: Bool {
|
|
|
244 |
#if os(iOS) || os(tvOS)
|
|
|
245 |
return contains(.isWWAN)
|
|
|
246 |
#else
|
|
|
247 |
return false
|
|
|
248 |
#endif
|
|
|
249 |
}
|
|
|
250 |
|
|
|
251 |
/// Human readable `String` for all states, to help with debugging.
|
|
|
252 |
var readableDescription: String {
|
|
|
253 |
let W = isCellular ? "W" : "-"
|
|
|
254 |
let R = isReachable ? "R" : "-"
|
|
|
255 |
let c = isConnectionRequired ? "c" : "-"
|
|
|
256 |
let t = contains(.transientConnection) ? "t" : "-"
|
|
|
257 |
let i = contains(.interventionRequired) ? "i" : "-"
|
|
|
258 |
let C = contains(.connectionOnTraffic) ? "C" : "-"
|
|
|
259 |
let D = contains(.connectionOnDemand) ? "D" : "-"
|
|
|
260 |
let l = contains(.isLocalAddress) ? "l" : "-"
|
|
|
261 |
let d = contains(.isDirect) ? "d" : "-"
|
|
|
262 |
let a = contains(.connectionAutomatic) ? "a" : "-"
|
|
|
263 |
|
|
|
264 |
return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)\(a)"
|
|
|
265 |
}
|
|
|
266 |
}
|
|
|
267 |
#endif
|