Reachability.swift 13 KB

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