Reachability.swift 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. /*
  2. Copyright (c) 2014, Ashley Mills
  3. All rights reserved.
  4. Redistribution and use in source and binary forms, with or without
  5. modification, are permitted provided that the following conditions are met:
  6. 1. Redistributions of source code must retain the above copyright notice, this
  7. list of conditions and the following disclaimer.
  8. 2. Redistributions in binary form must reproduce the above copyright notice,
  9. this list of conditions and the following disclaimer in the documentation
  10. and/or other materials provided with the distribution.
  11. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  12. AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  13. IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  14. ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  15. LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  16. CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  17. SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  18. INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  19. CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  20. ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  21. POSSIBILITY OF SUCH DAMAGE.
  22. */
  23. import SystemConfiguration
  24. import Foundation
  25. public enum ReachabilityError: Error {
  26. case FailedToCreateWithAddress(sockaddr_in)
  27. case FailedToCreateWithHostname(String)
  28. case UnableToSetCallback
  29. case UnableToSetDispatchQueue
  30. }
  31. public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification")
  32. func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) {
  33. guard let info = info else { return }
  34. let reachability = Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue()
  35. DispatchQueue.main.async {
  36. reachability.reachabilityChanged()
  37. }
  38. }
  39. public class Reachability {
  40. public typealias NetworkReachable = (Reachability) -> ()
  41. public typealias NetworkUnreachable = (Reachability) -> ()
  42. public enum NetworkStatus: CustomStringConvertible {
  43. case notReachable, reachableViaWiFi, reachableViaWWAN
  44. public var description: String {
  45. switch self {
  46. case .reachableViaWWAN: return "Cellular"
  47. case .reachableViaWiFi: return "WiFi"
  48. case .notReachable: return "No Connection"
  49. }
  50. }
  51. }
  52. public var whenReachable: NetworkReachable?
  53. public var whenUnreachable: NetworkUnreachable?
  54. public var reachableOnWWAN: Bool
  55. // The notification center on which "reachability changed" events are being posted
  56. public var notificationCenter: NotificationCenter = NotificationCenter.default
  57. public var currentReachabilityString: String {
  58. return "\(currentReachabilityStatus)"
  59. }
  60. public var currentReachabilityStatus: NetworkStatus {
  61. guard isReachable else { return .notReachable }
  62. if isReachableViaWiFi {
  63. return .reachableViaWiFi
  64. }
  65. if isRunningOnDevice {
  66. return .reachableViaWWAN
  67. }
  68. return .notReachable
  69. }
  70. fileprivate var previousFlags: SCNetworkReachabilityFlags?
  71. fileprivate var isRunningOnDevice: Bool = {
  72. #if (arch(i386) || arch(x86_64)) && os(iOS)
  73. return false
  74. #else
  75. return true
  76. #endif
  77. }()
  78. fileprivate var notifierRunning = false
  79. fileprivate let reachabilityRef: SCNetworkReachability
  80. fileprivate let reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability")
  81. required public init(reachabilityRef: SCNetworkReachability) {
  82. reachableOnWWAN = true
  83. self.reachabilityRef = reachabilityRef
  84. }
  85. public convenience init?(hostname: String) {
  86. guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { return nil }
  87. self.init(reachabilityRef: ref)
  88. }
  89. public convenience init?() {
  90. var zeroAddress = sockaddr()
  91. zeroAddress.sa_len = UInt8(MemoryLayout<sockaddr>.size)
  92. zeroAddress.sa_family = sa_family_t(AF_INET)
  93. guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { return nil }
  94. self.init(reachabilityRef: ref)
  95. }
  96. deinit {
  97. stopNotifier()
  98. }
  99. }
  100. public extension Reachability {
  101. // MARK: - *** Notifier methods ***
  102. func startNotifier() throws {
  103. guard !notifierRunning else { return }
  104. var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
  105. context.info = UnsafeMutableRawPointer(Unmanaged<Reachability>.passUnretained(self).toOpaque())
  106. if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) {
  107. stopNotifier()
  108. throw ReachabilityError.UnableToSetCallback
  109. }
  110. if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) {
  111. stopNotifier()
  112. throw ReachabilityError.UnableToSetDispatchQueue
  113. }
  114. // Perform an initial check
  115. reachabilitySerialQueue.async {
  116. self.reachabilityChanged()
  117. }
  118. notifierRunning = true
  119. }
  120. func stopNotifier() {
  121. defer { notifierRunning = false }
  122. SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil)
  123. SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
  124. }
  125. // MARK: - *** Connection test methods ***
  126. var isReachable: Bool {
  127. guard isReachableFlagSet else { return false }
  128. if isConnectionRequiredAndTransientFlagSet {
  129. return false
  130. }
  131. if isRunningOnDevice {
  132. if isOnWWANFlagSet && !reachableOnWWAN {
  133. // We don't want to connect when on 3G.
  134. return false
  135. }
  136. }
  137. return true
  138. }
  139. var isReachableViaWWAN: Bool {
  140. // Check we're not on the simulator, we're REACHABLE and check we're on WWAN
  141. return isRunningOnDevice && isReachableFlagSet && isOnWWANFlagSet
  142. }
  143. var isReachableViaWiFi: Bool {
  144. // Check we're reachable
  145. guard isReachableFlagSet else { return false }
  146. // If reachable we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
  147. guard isRunningOnDevice else { return true }
  148. // Check we're NOT on WWAN
  149. return !isOnWWANFlagSet
  150. }
  151. var description: String {
  152. let W = isRunningOnDevice ? (isOnWWANFlagSet ? "W" : "-") : "X"
  153. let R = isReachableFlagSet ? "R" : "-"
  154. let c = isConnectionRequiredFlagSet ? "c" : "-"
  155. let t = isTransientConnectionFlagSet ? "t" : "-"
  156. let i = isInterventionRequiredFlagSet ? "i" : "-"
  157. let C = isConnectionOnTrafficFlagSet ? "C" : "-"
  158. let D = isConnectionOnDemandFlagSet ? "D" : "-"
  159. let l = isLocalAddressFlagSet ? "l" : "-"
  160. let d = isDirectFlagSet ? "d" : "-"
  161. return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)"
  162. }
  163. }
  164. fileprivate extension Reachability {
  165. func reachabilityChanged() {
  166. let flags = reachabilityFlags
  167. guard previousFlags != flags else { return }
  168. let block = isReachable ? whenReachable : whenUnreachable
  169. block?(self)
  170. self.notificationCenter.post(name: ReachabilityChangedNotification, object:self)
  171. previousFlags = flags
  172. }
  173. var isOnWWANFlagSet: Bool {
  174. #if os(iOS)
  175. return reachabilityFlags.contains(.isWWAN)
  176. #else
  177. return false
  178. #endif
  179. }
  180. var isReachableFlagSet: Bool {
  181. return reachabilityFlags.contains(.reachable)
  182. }
  183. var isConnectionRequiredFlagSet: Bool {
  184. return reachabilityFlags.contains(.connectionRequired)
  185. }
  186. var isInterventionRequiredFlagSet: Bool {
  187. return reachabilityFlags.contains(.interventionRequired)
  188. }
  189. var isConnectionOnTrafficFlagSet: Bool {
  190. return reachabilityFlags.contains(.connectionOnTraffic)
  191. }
  192. var isConnectionOnDemandFlagSet: Bool {
  193. return reachabilityFlags.contains(.connectionOnDemand)
  194. }
  195. var isConnectionOnTrafficOrDemandFlagSet: Bool {
  196. return !reachabilityFlags.intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty
  197. }
  198. var isTransientConnectionFlagSet: Bool {
  199. return reachabilityFlags.contains(.transientConnection)
  200. }
  201. var isLocalAddressFlagSet: Bool {
  202. return reachabilityFlags.contains(.isLocalAddress)
  203. }
  204. var isDirectFlagSet: Bool {
  205. return reachabilityFlags.contains(.isDirect)
  206. }
  207. var isConnectionRequiredAndTransientFlagSet: Bool {
  208. return reachabilityFlags.intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]
  209. }
  210. var reachabilityFlags: SCNetworkReachabilityFlags {
  211. var flags = SCNetworkReachabilityFlags()
  212. if SCNetworkReachabilityGetFlags(reachabilityRef, &flags) {
  213. return flags
  214. } else {
  215. return SCNetworkReachabilityFlags()
  216. }
  217. }
  218. }