Reachability.m 14 KB

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