Reachability.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  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. enum ReachabilityError: ErrorType {
  26. case FailedToCreateWithAddress(sockaddr_in)
  27. case FailedToCreateWithHostname(String)
  28. case UnableToSetCallback
  29. case UnableToSetDispatchQueue
  30. }
  31. public let ReachabilityChangedNotification = "ReachabilityChangedNotification"
  32. func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutablePointer<Void>) {
  33. let reachability = Unmanaged<Reachability>.fromOpaque(COpaquePointer(info)).takeUnretainedValue()
  34. dispatch_async(dispatch_get_main_queue()) {
  35. reachability.reachabilityChanged(flags)
  36. }
  37. }
  38. public class Reachability: NSObject {
  39. public typealias NetworkReachable = (Reachability) -> ()
  40. public typealias NetworkUnreachable = (Reachability) -> ()
  41. public enum NetworkStatus: CustomStringConvertible {
  42. case NotReachable, ReachableViaWiFi, ReachableViaWWAN
  43. public var description: String {
  44. switch self {
  45. case .ReachableViaWWAN:
  46. return "Cellular"
  47. case .ReachableViaWiFi:
  48. return "WiFi"
  49. case .NotReachable:
  50. return "No Connection"
  51. }
  52. }
  53. }
  54. // MARK: - *** Public properties ***
  55. public var whenReachable: NetworkReachable?
  56. public var whenUnreachable: NetworkUnreachable?
  57. public var reachableOnWWAN: Bool
  58. public var notificationCenter = NSNotificationCenter.defaultCenter()
  59. public var currentReachabilityStatus: NetworkStatus {
  60. if isReachable() {
  61. if isReachableViaWiFi() {
  62. return .ReachableViaWiFi
  63. }
  64. if isRunningOnDevice {
  65. return .ReachableViaWWAN
  66. }
  67. }
  68. return .NotReachable
  69. }
  70. public var currentReachabilityString: String {
  71. return "\(currentReachabilityStatus)"
  72. }
  73. // MARK: - *** Initialisation methods ***
  74. required public init(reachabilityRef: SCNetworkReachability) {
  75. reachableOnWWAN = true
  76. self.reachabilityRef = reachabilityRef
  77. }
  78. public convenience init(hostname: String) throws {
  79. let nodename = (hostname as NSString).UTF8String
  80. guard let ref = SCNetworkReachabilityCreateWithName(nil, nodename) else { throw ReachabilityError.FailedToCreateWithHostname(hostname) }
  81. self.init(reachabilityRef: ref)
  82. }
  83. public class func reachabilityForInternetConnection() throws -> Reachability {
  84. var zeroAddress = sockaddr_in()
  85. zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
  86. zeroAddress.sin_family = sa_family_t(AF_INET)
  87. guard let ref = withUnsafePointer(&zeroAddress, {
  88. SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
  89. }) else { throw ReachabilityError.FailedToCreateWithAddress(zeroAddress) }
  90. return Reachability(reachabilityRef: ref)
  91. }
  92. public class func reachabilityForLocalWiFi() throws -> Reachability {
  93. var localWifiAddress: sockaddr_in = sockaddr_in(sin_len: __uint8_t(0), sin_family: sa_family_t(0), sin_port: in_port_t(0), sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
  94. localWifiAddress.sin_len = UInt8(sizeofValue(localWifiAddress))
  95. localWifiAddress.sin_family = sa_family_t(AF_INET)
  96. // IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
  97. let address: UInt32 = 0xA9FE0000
  98. localWifiAddress.sin_addr.s_addr = in_addr_t(address.bigEndian)
  99. guard let ref = withUnsafePointer(&localWifiAddress, {
  100. SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
  101. }) else { throw ReachabilityError.FailedToCreateWithAddress(localWifiAddress) }
  102. return Reachability(reachabilityRef: ref)
  103. }
  104. // MARK: - *** Notifier methods ***
  105. public func startNotifier() throws {
  106. if notifierRunning { return }
  107. var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
  108. context.info = UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque())
  109. if !SCNetworkReachabilitySetCallback(reachabilityRef!, callback, &context) {
  110. stopNotifier()
  111. throw ReachabilityError.UnableToSetCallback
  112. }
  113. if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef!, reachabilitySerialQueue) {
  114. stopNotifier()
  115. throw ReachabilityError.UnableToSetDispatchQueue
  116. }
  117. notifierRunning = true
  118. }
  119. public func stopNotifier() {
  120. if let reachabilityRef = reachabilityRef {
  121. SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil)
  122. SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
  123. }
  124. notifierRunning = false
  125. }
  126. // MARK: - *** Connection test methods ***
  127. public func isReachable() -> Bool {
  128. return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in
  129. return self.isReachableWithFlags(flags)
  130. })
  131. }
  132. public func isReachableViaWWAN() -> Bool {
  133. if isRunningOnDevice {
  134. return isReachableWithTest() { flags -> Bool in
  135. // Check we're REACHABLE
  136. if self.isReachable(flags) {
  137. // Now, check we're on WWAN
  138. if self.isOnWWAN(flags) {
  139. return true
  140. }
  141. }
  142. return false
  143. }
  144. }
  145. return false
  146. }
  147. public func isReachableViaWiFi() -> Bool {
  148. return isReachableWithTest() { flags -> Bool in
  149. // Check we're reachable
  150. if self.isReachable(flags) {
  151. if self.isRunningOnDevice {
  152. // Check we're NOT on WWAN
  153. if self.isOnWWAN(flags) {
  154. return false
  155. }
  156. }
  157. return true
  158. }
  159. return false
  160. }
  161. }
  162. // MARK: - *** Private methods ***
  163. private var isRunningOnDevice: Bool = {
  164. #if (arch(i386) || arch(x86_64)) && os(iOS)
  165. return false
  166. #else
  167. return true
  168. #endif
  169. }()
  170. private var notifierRunning = false
  171. private var reachabilityRef: SCNetworkReachability?
  172. private let reachabilitySerialQueue = dispatch_queue_create("uk.co.ashleymills.reachability", DISPATCH_QUEUE_SERIAL)
  173. private func reachabilityChanged(flags: SCNetworkReachabilityFlags) {
  174. if isReachableWithFlags(flags) {
  175. if let block = whenReachable {
  176. block(self)
  177. }
  178. } else {
  179. if let block = whenUnreachable {
  180. block(self)
  181. }
  182. }
  183. notificationCenter.postNotificationName(ReachabilityChangedNotification, object:self)
  184. }
  185. private func isReachableWithFlags(flags: SCNetworkReachabilityFlags) -> Bool {
  186. let reachable = isReachable(flags)
  187. if !reachable {
  188. return false
  189. }
  190. if isConnectionRequiredOrTransient(flags) {
  191. return false
  192. }
  193. if isRunningOnDevice {
  194. if isOnWWAN(flags) && !reachableOnWWAN {
  195. // We don't want to connect when on 3G.
  196. return false
  197. }
  198. }
  199. return true
  200. }
  201. private func isReachableWithTest(test: (SCNetworkReachabilityFlags) -> (Bool)) -> Bool {
  202. if let reachabilityRef = reachabilityRef {
  203. var flags = SCNetworkReachabilityFlags(rawValue: 0)
  204. let gotFlags = withUnsafeMutablePointer(&flags) {
  205. SCNetworkReachabilityGetFlags(reachabilityRef, UnsafeMutablePointer($0))
  206. }
  207. if gotFlags {
  208. return test(flags)
  209. }
  210. }
  211. return false
  212. }
  213. // WWAN may be available, but not active until a connection has been established.
  214. // WiFi may require a connection for VPN on Demand.
  215. private func isConnectionRequired() -> Bool {
  216. return connectionRequired()
  217. }
  218. private func connectionRequired() -> Bool {
  219. return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in
  220. return self.isConnectionRequired(flags)
  221. })
  222. }
  223. // Dynamic, on demand connection?
  224. private func isConnectionOnDemand() -> Bool {
  225. return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in
  226. return self.isConnectionRequired(flags) && self.isConnectionOnTrafficOrDemand(flags)
  227. })
  228. }
  229. // Is user intervention required?
  230. private func isInterventionRequired() -> Bool {
  231. return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in
  232. return self.isConnectionRequired(flags) && self.isInterventionRequired(flags)
  233. })
  234. }
  235. private func isOnWWAN(flags: SCNetworkReachabilityFlags) -> Bool {
  236. #if os(iOS)
  237. return flags.contains(.IsWWAN)
  238. #else
  239. return false
  240. #endif
  241. }
  242. private func isReachable(flags: SCNetworkReachabilityFlags) -> Bool {
  243. return flags.contains(.Reachable)
  244. }
  245. private func isConnectionRequired(flags: SCNetworkReachabilityFlags) -> Bool {
  246. return flags.contains(.ConnectionRequired)
  247. }
  248. private func isInterventionRequired(flags: SCNetworkReachabilityFlags) -> Bool {
  249. return flags.contains(.InterventionRequired)
  250. }
  251. private func isConnectionOnTraffic(flags: SCNetworkReachabilityFlags) -> Bool {
  252. return flags.contains(.ConnectionOnTraffic)
  253. }
  254. private func isConnectionOnDemand(flags: SCNetworkReachabilityFlags) -> Bool {
  255. return flags.contains(.ConnectionOnDemand)
  256. }
  257. func isConnectionOnTrafficOrDemand(flags: SCNetworkReachabilityFlags) -> Bool {
  258. return !flags.intersect([.ConnectionOnTraffic, .ConnectionOnDemand]).isEmpty
  259. }
  260. private func isTransientConnection(flags: SCNetworkReachabilityFlags) -> Bool {
  261. return flags.contains(.TransientConnection)
  262. }
  263. private func isLocalAddress(flags: SCNetworkReachabilityFlags) -> Bool {
  264. return flags.contains(.IsLocalAddress)
  265. }
  266. private func isDirect(flags: SCNetworkReachabilityFlags) -> Bool {
  267. return flags.contains(.IsDirect)
  268. }
  269. private func isConnectionRequiredOrTransient(flags: SCNetworkReachabilityFlags) -> Bool {
  270. let testcase:SCNetworkReachabilityFlags = [.ConnectionRequired, .TransientConnection]
  271. return flags.intersect(testcase) == testcase
  272. }
  273. private var reachabilityFlags: SCNetworkReachabilityFlags {
  274. if let reachabilityRef = reachabilityRef {
  275. var flags = SCNetworkReachabilityFlags(rawValue: 0)
  276. let gotFlags = withUnsafeMutablePointer(&flags) {
  277. SCNetworkReachabilityGetFlags(reachabilityRef, UnsafeMutablePointer($0))
  278. }
  279. if gotFlags {
  280. return flags
  281. }
  282. }
  283. return []
  284. }
  285. override public var description: String {
  286. var W: String
  287. if isRunningOnDevice {
  288. W = isOnWWAN(reachabilityFlags) ? "W" : "-"
  289. } else {
  290. W = "X"
  291. }
  292. let R = isReachable(reachabilityFlags) ? "R" : "-"
  293. let c = isConnectionRequired(reachabilityFlags) ? "c" : "-"
  294. let t = isTransientConnection(reachabilityFlags) ? "t" : "-"
  295. let i = isInterventionRequired(reachabilityFlags) ? "i" : "-"
  296. let C = isConnectionOnTraffic(reachabilityFlags) ? "C" : "-"
  297. let D = isConnectionOnDemand(reachabilityFlags) ? "D" : "-"
  298. let l = isLocalAddress(reachabilityFlags) ? "l" : "-"
  299. let d = isDirect(reachabilityFlags) ? "d" : "-"
  300. return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)"
  301. }
  302. deinit {
  303. stopNotifier()
  304. reachabilityRef = nil
  305. whenReachable = nil
  306. whenUnreachable = nil
  307. }
  308. }