Reachability.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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. // Reachability.swift version 2.2beta2
  24. import SystemConfiguration
  25. import Foundation
  26. public enum ReachabilityError: Error {
  27. case FailedToCreateWithAddress(sockaddr_in)
  28. case FailedToCreateWithHostname(String)
  29. case UnableToSetCallback
  30. case UnableToSetDispatchQueue
  31. }
  32. public let ReachabilityChangedNotification = "ReachabilityChangedNotification" as NSNotification.Name
  33. func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutablePointer<Void>?) {
  34. guard let info = info else { return }
  35. let reachability = Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue()
  36. DispatchQueue.main.async {
  37. reachability.reachabilityChanged(flags:flags)
  38. }
  39. }
  40. public class Reachability: NSObject {
  41. public typealias NetworkReachable = (Reachability) -> ()
  42. public typealias NetworkUnreachable = (Reachability) -> ()
  43. public enum NetworkStatus: CustomStringConvertible {
  44. case NotReachable, ReachableViaWiFi, ReachableViaWWAN
  45. public var description: String {
  46. switch self {
  47. case .ReachableViaWWAN:
  48. return "Cellular"
  49. case .ReachableViaWiFi:
  50. return "WiFi"
  51. case .NotReachable:
  52. return "No Connection"
  53. }
  54. }
  55. }
  56. public var whenReachable: NetworkReachable?
  57. public var whenUnreachable: NetworkUnreachable?
  58. public var reachableOnWWAN: Bool
  59. public var currentReachabilityString: String {
  60. return "\(currentReachabilityStatus)"
  61. }
  62. public var currentReachabilityStatus: NetworkStatus {
  63. if isReachable {
  64. if isReachableViaWiFi {
  65. return .ReachableViaWiFi
  66. }
  67. if isRunningOnDevice {
  68. return .ReachableViaWWAN
  69. }
  70. }
  71. return .NotReachable
  72. }
  73. private var previousFlags: SCNetworkReachabilityFlags?
  74. private var isRunningOnDevice: Bool = {
  75. #if (arch(i386) || arch(x86_64)) && os(iOS)
  76. return false
  77. #else
  78. return true
  79. #endif
  80. }()
  81. private var notifierRunning = false
  82. private var reachabilityRef: SCNetworkReachability?
  83. private let reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability")
  84. required public init(reachabilityRef: SCNetworkReachability) {
  85. reachableOnWWAN = true
  86. self.reachabilityRef = reachabilityRef
  87. }
  88. deinit {
  89. stopNotifier()
  90. reachabilityRef = nil
  91. whenReachable = nil
  92. whenUnreachable = nil
  93. }
  94. }
  95. public extension Reachability {
  96. convenience init(hostname: String) throws {
  97. guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { throw ReachabilityError.FailedToCreateWithHostname(hostname) }
  98. self.init(reachabilityRef: ref)
  99. }
  100. class func reachabilityForInternetConnection() throws -> Reachability {
  101. var zeroAddress = sockaddr_in()
  102. zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  103. zeroAddress.sin_family = sa_family_t(AF_INET)
  104. guard let ref = withUnsafePointer(&zeroAddress, {
  105. SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
  106. }) else { throw ReachabilityError.FailedToCreateWithAddress(zeroAddress) }
  107. return Reachability(reachabilityRef: ref)
  108. }
  109. // MARK: - *** Notifier methods ***
  110. func startNotifier() throws {
  111. guard let reachabilityRef = reachabilityRef, !notifierRunning else { return }
  112. var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
  113. context.info = UnsafeMutablePointer<Void>(Unmanaged<Reachability>.passUnretained(self).toOpaque())
  114. if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) {
  115. stopNotifier()
  116. throw ReachabilityError.UnableToSetCallback
  117. }
  118. if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) {
  119. stopNotifier()
  120. throw ReachabilityError.UnableToSetDispatchQueue
  121. }
  122. // Perform an intial check
  123. reachabilitySerialQueue.async {
  124. let flags = self.reachabilityFlags
  125. self.reachabilityChanged(flags: flags)
  126. }
  127. notifierRunning = true
  128. }
  129. func stopNotifier() {
  130. defer { notifierRunning = false }
  131. guard let reachabilityRef = reachabilityRef else { return }
  132. SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil)
  133. SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
  134. }
  135. // MARK: - *** Connection test methods ***
  136. var isReachable: Bool {
  137. let flags = reachabilityFlags
  138. return isReachable(flags:flags)
  139. }
  140. var isReachableViaWWAN: Bool {
  141. let flags = reachabilityFlags
  142. // Check we're not on the simulator, we're REACHABLE and check we're on WWAN
  143. return isRunningOnDevice && isReachable(flags:flags) && isOnWWAN(flags:flags)
  144. }
  145. var isReachableViaWiFi: Bool {
  146. let flags = reachabilityFlags
  147. // Check we're reachable
  148. guard isReachable(flags:flags) else { return false }
  149. // If reachable we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
  150. guard isRunningOnDevice else { return true }
  151. // Check we're NOT on WWAN
  152. return !isOnWWAN(flags:flags)
  153. }
  154. override var description: String {
  155. let W = isRunningOnDevice ? (isOnWWAN(flags: reachabilityFlags) ? "W" : "-") : "X"
  156. let R = isReachable(flags: reachabilityFlags) ? "R" : "-"
  157. let c = isConnectionRequired(flags: reachabilityFlags) ? "c" : "-"
  158. let t = isTransientConnection(flags: reachabilityFlags) ? "t" : "-"
  159. let i = isInterventionRequired(flags: reachabilityFlags) ? "i" : "-"
  160. let C = isConnectionOnTraffic(flags: reachabilityFlags) ? "C" : "-"
  161. let D = isConnectionOnDemand(flags: reachabilityFlags) ? "D" : "-"
  162. let l = isLocalAddress(flags: reachabilityFlags) ? "l" : "-"
  163. let d = isDirect(flags: reachabilityFlags) ? "d" : "-"
  164. return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)"
  165. }
  166. }
  167. private extension Reachability {
  168. func reachabilityChanged(flags:SCNetworkReachabilityFlags) {
  169. guard previousFlags != flags else { return }
  170. let block = isReachable(withFlags: flags) ? whenReachable : whenUnreachable
  171. block?(self)
  172. NotificationCenter.default.post(name: ReachabilityChangedNotification, object:self)
  173. previousFlags = flags
  174. }
  175. func isReachable(withFlags flags:SCNetworkReachabilityFlags) -> Bool {
  176. if !isReachable(flags: flags) {
  177. return false
  178. }
  179. if isConnectionRequiredOrTransient(flags: flags) {
  180. return false
  181. }
  182. if isRunningOnDevice {
  183. if isOnWWAN(flags: flags) && !reachableOnWWAN {
  184. // We don't want to connect when on 3G.
  185. return false
  186. }
  187. }
  188. return true
  189. }
  190. // WWAN may be available, but not active until a connection has been established.
  191. // WiFi may require a connection for VPN on Demand.
  192. func isConnectionRequired() -> Bool {
  193. return connectionRequired()
  194. }
  195. func connectionRequired() -> Bool {
  196. let flags = reachabilityFlags
  197. return isConnectionRequired(flags: flags)
  198. }
  199. // Dynamic, on demand connection?
  200. func isConnectionOnDemand() -> Bool {
  201. let flags = reachabilityFlags
  202. return isConnectionRequired(flags: flags) && isConnectionOnTrafficOrDemand(flags: flags)
  203. }
  204. // Is user intervention required?
  205. func isInterventionRequired() -> Bool {
  206. let flags = reachabilityFlags
  207. return isConnectionRequired(flags: flags) && isInterventionRequired(flags: flags)
  208. }
  209. func isOnWWAN(flags:SCNetworkReachabilityFlags) -> Bool {
  210. #if os(iOS)
  211. return flags.contains(.isWWAN)
  212. #else
  213. return false
  214. #endif
  215. }
  216. func isReachable(flags:SCNetworkReachabilityFlags) -> Bool {
  217. return flags.contains(.reachable)
  218. }
  219. func isConnectionRequired(flags:SCNetworkReachabilityFlags) -> Bool {
  220. return flags.contains(.connectionRequired)
  221. }
  222. func isInterventionRequired(flags:SCNetworkReachabilityFlags) -> Bool {
  223. return flags.contains(.interventionRequired)
  224. }
  225. func isConnectionOnTraffic(flags:SCNetworkReachabilityFlags) -> Bool {
  226. return flags.contains(.connectionOnTraffic)
  227. }
  228. func isConnectionOnDemand(flags:SCNetworkReachabilityFlags) -> Bool {
  229. return flags.contains(.connectionOnDemand)
  230. }
  231. func isConnectionOnTrafficOrDemand(flags:SCNetworkReachabilityFlags) -> Bool {
  232. return !flags.intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty
  233. }
  234. func isTransientConnection(flags:SCNetworkReachabilityFlags) -> Bool {
  235. return flags.contains(.transientConnection)
  236. }
  237. func isLocalAddress(flags:SCNetworkReachabilityFlags) -> Bool {
  238. return flags.contains(.isLocalAddress)
  239. }
  240. func isDirect(flags:SCNetworkReachabilityFlags) -> Bool {
  241. return flags.contains(.isDirect)
  242. }
  243. func isConnectionRequiredOrTransient(flags:SCNetworkReachabilityFlags) -> Bool {
  244. let testcase:SCNetworkReachabilityFlags = [.connectionRequired, .transientConnection]
  245. return flags.intersection(testcase) == testcase
  246. }
  247. var reachabilityFlags: SCNetworkReachabilityFlags {
  248. guard let reachabilityRef = reachabilityRef else { return SCNetworkReachabilityFlags() }
  249. var flags = SCNetworkReachabilityFlags()
  250. let gotFlags = withUnsafeMutablePointer(&flags) {
  251. SCNetworkReachabilityGetFlags(reachabilityRef, UnsafeMutablePointer($0))
  252. }
  253. if gotFlags {
  254. return flags
  255. } else {
  256. return SCNetworkReachabilityFlags()
  257. }
  258. }
  259. }