Reachability.m 13 KB

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