Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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