Reachability.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. /*
  2. Copyright (c) 2011, Tony Million.
  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 "Reachability.h"
  24. NSString *const kReachabilityChangedNotification = @"kReachabilityChangedNotification";
  25. @interface Reachability ()
  26. @property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef;
  27. #if NEEDS_DISPATCH_RETAIN_RELEASE
  28. @property (nonatomic, assign) dispatch_queue_t reachabilitySerialQueue;
  29. #else
  30. @property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue;
  31. #endif
  32. @property (nonatomic, strong) id reachabilityObject;
  33. -(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags;
  34. -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags;
  35. @end
  36. static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags)
  37. {
  38. return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c",
  39. #if TARGET_OS_IPHONE
  40. (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-',
  41. #else
  42. 'X',
  43. #endif
  44. (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-',
  45. (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-',
  46. (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-',
  47. (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-',
  48. (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-',
  49. (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-',
  50. (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-',
  51. (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-'];
  52. }
  53. // Start listening for reachability notifications on the current run loop
  54. static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
  55. {
  56. #pragma unused (target)
  57. #if __has_feature(objc_arc)
  58. Reachability *reachability = ((__bridge Reachability*)info);
  59. #else
  60. Reachability *reachability = ((Reachability*)info);
  61. #endif
  62. // We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool,
  63. // but what the heck eh?
  64. @autoreleasepool
  65. {
  66. [reachability reachabilityChanged:flags];
  67. }
  68. }
  69. @implementation Reachability
  70. @synthesize reachabilityRef;
  71. @synthesize reachabilitySerialQueue;
  72. @synthesize reachableOnWWAN;
  73. @synthesize reachableBlock;
  74. @synthesize unreachableBlock;
  75. @synthesize reachabilityObject;
  76. #pragma mark - Class Constructor Methods
  77. +(Reachability*)reachabilityWithHostName:(NSString*)hostname
  78. {
  79. return [Reachability reachabilityWithHostname:hostname];
  80. }
  81. +(Reachability*)reachabilityWithHostname:(NSString*)hostname
  82. {
  83. SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]);
  84. if (ref)
  85. {
  86. id reachability = [[self alloc] initWithReachabilityRef:ref];
  87. #if __has_feature(objc_arc)
  88. return reachability;
  89. #else
  90. return [reachability autorelease];
  91. #endif
  92. }
  93. return nil;
  94. }
  95. +(Reachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress
  96. {
  97. SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
  98. if (ref)
  99. {
  100. id reachability = [[self alloc] initWithReachabilityRef:ref];
  101. #if __has_feature(objc_arc)
  102. return reachability;
  103. #else
  104. return [reachability autorelease];
  105. #endif
  106. }
  107. return nil;
  108. }
  109. +(Reachability *)reachabilityForInternetConnection
  110. {
  111. struct sockaddr_in zeroAddress;
  112. bzero(&zeroAddress, sizeof(zeroAddress));
  113. zeroAddress.sin_len = sizeof(zeroAddress);
  114. zeroAddress.sin_family = AF_INET;
  115. return [self reachabilityWithAddress:&zeroAddress];
  116. }
  117. +(Reachability*)reachabilityForLocalWiFi
  118. {
  119. struct sockaddr_in localWifiAddress;
  120. bzero(&localWifiAddress, sizeof(localWifiAddress));
  121. localWifiAddress.sin_len = sizeof(localWifiAddress);
  122. localWifiAddress.sin_family = AF_INET;
  123. // IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
  124. localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM);
  125. return [self reachabilityWithAddress:&localWifiAddress];
  126. }
  127. // Initialization methods
  128. -(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref
  129. {
  130. self = [super init];
  131. if (self != nil)
  132. {
  133. self.reachableOnWWAN = YES;
  134. self.reachabilityRef = ref;
  135. }
  136. return self;
  137. }
  138. -(void)dealloc
  139. {
  140. [self stopNotifier];
  141. if(self.reachabilityRef)
  142. {
  143. CFRelease(self.reachabilityRef);
  144. self.reachabilityRef = nil;
  145. }
  146. self.reachableBlock = nil;
  147. self.unreachableBlock = nil;
  148. #if !(__has_feature(objc_arc))
  149. [super dealloc];
  150. #endif
  151. }
  152. #pragma mark - Notifier Methods
  153. // Notifier
  154. // NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD
  155. // - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS.
  156. // INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want)
  157. -(BOOL)startNotifier
  158. {
  159. SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL };
  160. // this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves
  161. // woah
  162. self.reachabilityObject = self;
  163. // First, we need to create a serial queue.
  164. // We allocate this once for the lifetime of the notifier.
  165. self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL);
  166. if(!self.reachabilitySerialQueue)
  167. {
  168. return NO;
  169. }
  170. #if __has_feature(objc_arc)
  171. context.info = (__bridge void *)self;
  172. #else
  173. context.info = (void *)self;
  174. #endif
  175. if (!SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context))
  176. {
  177. #ifdef DEBUG
  178. NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError()));
  179. #endif
  180. // Clear out the dispatch queue
  181. if(self.reachabilitySerialQueue)
  182. {
  183. #if NEEDS_DISPATCH_RETAIN_RELEASE
  184. dispatch_release(self.reachabilitySerialQueue);
  185. #endif
  186. self.reachabilitySerialQueue = nil;
  187. }
  188. self.reachabilityObject = nil;
  189. return NO;
  190. }
  191. // Set it as our reachability queue, which will retain the queue
  192. if(!SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue))
  193. {
  194. #ifdef DEBUG
  195. NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError()));
  196. #endif
  197. // UH OH - FAILURE!
  198. // First stop, any callbacks!
  199. SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
  200. // Then clear out the dispatch queue.
  201. if(self.reachabilitySerialQueue)
  202. {
  203. #if NEEDS_DISPATCH_RETAIN_RELEASE
  204. dispatch_release(self.reachabilitySerialQueue);
  205. #endif
  206. self.reachabilitySerialQueue = nil;
  207. }
  208. self.reachabilityObject = nil;
  209. return NO;
  210. }
  211. return YES;
  212. }
  213. -(void)stopNotifier
  214. {
  215. // First stop, any callbacks!
  216. SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL);
  217. // Unregister target from the GCD serial dispatch queue.
  218. SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL);
  219. if(self.reachabilitySerialQueue)
  220. {
  221. #if NEEDS_DISPATCH_RETAIN_RELEASE
  222. dispatch_release(self.reachabilitySerialQueue);
  223. #endif
  224. self.reachabilitySerialQueue = nil;
  225. }
  226. self.reachabilityObject = nil;
  227. }
  228. #pragma mark - reachability tests
  229. // This is for the case where you flick the airplane mode;
  230. // you end up getting something like this:
  231. //Reachability: WR ct-----
  232. //Reachability: -- -------
  233. //Reachability: WR ct-----
  234. //Reachability: -- -------
  235. // We treat this as 4 UNREACHABLE triggers - really apple should do better than this
  236. #define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection)
  237. -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags
  238. {
  239. BOOL connectionUP = YES;
  240. if(!(flags & kSCNetworkReachabilityFlagsReachable))
  241. connectionUP = NO;
  242. if( (flags & testcase) == testcase )
  243. connectionUP = NO;
  244. #if TARGET_OS_IPHONE
  245. if(flags & kSCNetworkReachabilityFlagsIsWWAN)
  246. {
  247. // We're on 3G.
  248. if(!self.reachableOnWWAN)
  249. {
  250. // We don't want to connect when on 3G.
  251. connectionUP = NO;
  252. }
  253. }
  254. #endif
  255. return connectionUP;
  256. }
  257. -(BOOL)isReachable
  258. {
  259. SCNetworkReachabilityFlags flags;
  260. if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags))
  261. return NO;
  262. return [self isReachableWithFlags:flags];
  263. }
  264. -(BOOL)isReachableViaWWAN
  265. {
  266. #if TARGET_OS_IPHONE
  267. SCNetworkReachabilityFlags flags = 0;
  268. if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
  269. {
  270. // Check we're REACHABLE
  271. if(flags & kSCNetworkReachabilityFlagsReachable)
  272. {
  273. // Now, check we're on WWAN
  274. if(flags & kSCNetworkReachabilityFlagsIsWWAN)
  275. {
  276. return YES;
  277. }
  278. }
  279. }
  280. #endif
  281. return NO;
  282. }
  283. -(BOOL)isReachableViaWiFi
  284. {
  285. SCNetworkReachabilityFlags flags = 0;
  286. if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
  287. {
  288. // Check we're reachable
  289. if((flags & kSCNetworkReachabilityFlagsReachable))
  290. {
  291. #if TARGET_OS_IPHONE
  292. // Check we're NOT on WWAN
  293. if((flags & kSCNetworkReachabilityFlagsIsWWAN))
  294. {
  295. return NO;
  296. }
  297. #endif
  298. return YES;
  299. }
  300. }
  301. return NO;
  302. }
  303. // WWAN may be available, but not active until a connection has been established.
  304. // WiFi may require a connection for VPN on Demand.
  305. -(BOOL)isConnectionRequired
  306. {
  307. return [self connectionRequired];
  308. }
  309. -(BOOL)connectionRequired
  310. {
  311. SCNetworkReachabilityFlags flags;
  312. if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
  313. {
  314. return (flags & kSCNetworkReachabilityFlagsConnectionRequired);
  315. }
  316. return NO;
  317. }
  318. // Dynamic, on demand connection?
  319. -(BOOL)isConnectionOnDemand
  320. {
  321. SCNetworkReachabilityFlags flags;
  322. if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
  323. {
  324. return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
  325. (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand)));
  326. }
  327. return NO;
  328. }
  329. // Is user intervention required?
  330. -(BOOL)isInterventionRequired
  331. {
  332. SCNetworkReachabilityFlags flags;
  333. if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
  334. {
  335. return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) &&
  336. (flags & kSCNetworkReachabilityFlagsInterventionRequired));
  337. }
  338. return NO;
  339. }
  340. #pragma mark - reachability status stuff
  341. -(NetworkStatus)currentReachabilityStatus
  342. {
  343. if([self isReachable])
  344. {
  345. if([self isReachableViaWiFi])
  346. return ReachableViaWiFi;
  347. #if TARGET_OS_IPHONE
  348. return ReachableViaWWAN;
  349. #endif
  350. }
  351. return NotReachable;
  352. }
  353. -(SCNetworkReachabilityFlags)reachabilityFlags
  354. {
  355. SCNetworkReachabilityFlags flags = 0;
  356. if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags))
  357. {
  358. return flags;
  359. }
  360. return 0;
  361. }
  362. -(NSString*)currentReachabilityString
  363. {
  364. NetworkStatus temp = [self currentReachabilityStatus];
  365. if(temp == reachableOnWWAN)
  366. {
  367. // Updated for the fact that we have CDMA phones now!
  368. return NSLocalizedString(@"Cellular", @"");
  369. }
  370. if (temp == ReachableViaWiFi)
  371. {
  372. return NSLocalizedString(@"WiFi", @"");
  373. }
  374. return NSLocalizedString(@"No Connection", @"");
  375. }
  376. -(NSString*)currentReachabilityFlags
  377. {
  378. return reachabilityFlags([self reachabilityFlags]);
  379. }
  380. #pragma mark - Callback function calls this method
  381. -(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags
  382. {
  383. if([self isReachableWithFlags:flags])
  384. {
  385. if(self.reachableBlock)
  386. {
  387. self.reachableBlock(self);
  388. }
  389. }
  390. else
  391. {
  392. if(self.unreachableBlock)
  393. {
  394. self.unreachableBlock(self);
  395. }
  396. }
  397. // this makes sure the change notification happens on the MAIN THREAD
  398. dispatch_async(dispatch_get_main_queue(), ^{
  399. [[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification
  400. object:self];
  401. });
  402. }
  403. #pragma mark - Debug Description
  404. - (NSString *) description;
  405. {
  406. NSString *description = [NSString stringWithFormat:@"<%@: %#x>",
  407. NSStringFromClass([self class]), (unsigned int) self];
  408. return description;
  409. }
  410. @end