Reachability.swift 13 KB

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