FIRReachabilityChecker.m 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. // Copyright 2017 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "Private/FIRReachabilityChecker.h"
  15. #import "Private/FIRReachabilityChecker+Internal.h"
  16. #import "Private/FIRNetwork.h"
  17. #import "Private/FIRNetworkMessageCode.h"
  18. #import "Private/FIRLogger.h"
  19. static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
  20. SCNetworkReachabilityFlags flags, void *info);
  21. static const struct FIRReachabilityApi kFIRDefaultReachabilityApi = {
  22. SCNetworkReachabilityCreateWithName,
  23. SCNetworkReachabilitySetCallback,
  24. SCNetworkReachabilityScheduleWithRunLoop,
  25. SCNetworkReachabilityUnscheduleFromRunLoop,
  26. CFRelease,
  27. };
  28. static NSString *const kFIRReachabilityUnknownStatus = @"Unknown";
  29. static NSString *const kFIRReachabilityConnectedStatus = @"Connected";
  30. static NSString *const kFIRReachabilityDisconnectedStatus = @"Disconnected";
  31. @interface FIRReachabilityChecker ()
  32. @property(nonatomic, assign) const struct FIRReachabilityApi *reachabilityApi;
  33. @property(nonatomic, assign) FIRReachabilityStatus reachabilityStatus;
  34. @property(nonatomic, copy) NSString *host;
  35. @property(nonatomic, assign) SCNetworkReachabilityRef reachability;
  36. @end
  37. @implementation FIRReachabilityChecker
  38. @synthesize reachabilityApi = reachabilityApi_;
  39. @synthesize reachability = reachability_;
  40. - (const struct FIRReachabilityApi *)reachabilityApi {
  41. return reachabilityApi_;
  42. }
  43. - (void)setReachabilityApi:(const struct FIRReachabilityApi *)reachabilityApi {
  44. if (reachability_) {
  45. NSString *message = @"Cannot change reachability API while reachability is running. "
  46. @"Call stop first.";
  47. [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError
  48. messageCode:kFIRNetworkMessageCodeReachabilityChecker000
  49. message:message];
  50. return;
  51. }
  52. reachabilityApi_ = reachabilityApi;
  53. }
  54. @synthesize reachabilityStatus = reachabilityStatus_;
  55. @synthesize host = host_;
  56. @synthesize reachabilityDelegate = reachabilityDelegate_;
  57. @synthesize loggerDelegate = loggerDelegate_;
  58. - (BOOL)isActive {
  59. return reachability_ != nil;
  60. }
  61. - (void)setReachabilityDelegate:(id<FIRReachabilityDelegate>)reachabilityDelegate {
  62. if (reachabilityDelegate &&
  63. (![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(FIRReachabilityDelegate)])) {
  64. FIRLogError(
  65. kFIRLoggerCore,
  66. [NSString stringWithFormat:@"I-NET%06ld",
  67. (long)kFIRNetworkMessageCodeReachabilityChecker005],
  68. @"Reachability delegate doesn't conform to Reachability protocol.");
  69. return;
  70. }
  71. reachabilityDelegate_ = reachabilityDelegate;
  72. }
  73. - (void)setLoggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate {
  74. if (loggerDelegate &&
  75. (![(NSObject *)loggerDelegate conformsToProtocol:@protocol(FIRNetworkLoggerDelegate)])) {
  76. FIRLogError(
  77. kFIRLoggerCore,
  78. [NSString stringWithFormat:@"I-NET%06ld",
  79. (long)kFIRNetworkMessageCodeReachabilityChecker006],
  80. @"Reachability delegate doesn't conform to Logger protocol.");
  81. return;
  82. }
  83. loggerDelegate_ = loggerDelegate;
  84. }
  85. - (instancetype)initWithReachabilityDelegate:(id<FIRReachabilityDelegate>)reachabilityDelegate
  86. loggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate
  87. withHost:(NSString *)host {
  88. self = [super init];
  89. [self setLoggerDelegate:loggerDelegate];
  90. if (!host || !host.length) {
  91. [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError
  92. messageCode:kFIRNetworkMessageCodeReachabilityChecker001
  93. message:@"Invalid host specified"];
  94. return nil;
  95. }
  96. if (self) {
  97. [self setReachabilityDelegate:reachabilityDelegate];
  98. reachabilityApi_ = &kFIRDefaultReachabilityApi;
  99. reachabilityStatus_ = kFIRReachabilityUnknown;
  100. host_ = [host copy];
  101. reachability_ = nil;
  102. }
  103. return self;
  104. }
  105. - (void)dealloc {
  106. reachabilityDelegate_ = nil;
  107. loggerDelegate_ = nil;
  108. [self stop];
  109. }
  110. - (BOOL)start {
  111. if (!reachability_) {
  112. reachability_ = reachabilityApi_->createWithNameFn(kCFAllocatorDefault, [host_ UTF8String]);
  113. if (!reachability_) {
  114. return NO;
  115. }
  116. SCNetworkReachabilityContext context = {
  117. 0, /* version */
  118. (__bridge void *)(self), /* info (passed as last parameter to reachability callback) */
  119. NULL, /* retain */
  120. NULL, /* release */
  121. NULL /* copyDescription */
  122. };
  123. if (!reachabilityApi_->setCallbackFn(reachability_, ReachabilityCallback, &context) ||
  124. !reachabilityApi_->scheduleWithRunLoopFn(reachability_, CFRunLoopGetMain(),
  125. kCFRunLoopCommonModes)) {
  126. reachabilityApi_->releaseFn(reachability_);
  127. reachability_ = nil;
  128. [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError
  129. messageCode:kFIRNetworkMessageCodeReachabilityChecker002
  130. message:@"Failed to start reachability handle"];
  131. return NO;
  132. }
  133. }
  134. [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
  135. messageCode:kFIRNetworkMessageCodeReachabilityChecker003
  136. message:@"Monitoring the network status"];
  137. return YES;
  138. }
  139. - (void)stop {
  140. if (reachability_) {
  141. reachabilityStatus_ = kFIRReachabilityUnknown;
  142. reachabilityApi_->unscheduleFromRunLoopFn(reachability_, CFRunLoopGetMain(),
  143. kCFRunLoopCommonModes);
  144. reachabilityApi_->releaseFn(reachability_);
  145. reachability_ = nil;
  146. }
  147. }
  148. - (FIRReachabilityStatus)statusForFlags:(SCNetworkReachabilityFlags)flags {
  149. FIRReachabilityStatus status = kFIRReachabilityNotReachable;
  150. // If the Reachable flag is not set, we definitely don't have connectivity.
  151. if (flags & kSCNetworkReachabilityFlagsReachable) {
  152. // Reachable flag is set. Check further flags.
  153. if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) {
  154. // Connection required flag is not set, so we have connectivity.
  155. status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular
  156. : kFIRReachabilityViaWifi;
  157. } else if ((flags & (kSCNetworkReachabilityFlagsConnectionOnDemand |
  158. kSCNetworkReachabilityFlagsConnectionOnTraffic)) &&
  159. !(flags & kSCNetworkReachabilityFlagsInterventionRequired)) {
  160. // If the connection on demand or connection on traffic flag is set, and user intervention
  161. // is not required, we have connectivity.
  162. status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular
  163. : kFIRReachabilityViaWifi;
  164. }
  165. }
  166. return status;
  167. }
  168. - (void)reachabilityFlagsChanged:(SCNetworkReachabilityFlags)flags {
  169. FIRReachabilityStatus status = [self statusForFlags:flags];
  170. if (reachabilityStatus_ != status) {
  171. NSString *reachabilityStatusString;
  172. if (status == kFIRReachabilityUnknown) {
  173. reachabilityStatusString = kFIRReachabilityUnknownStatus;
  174. } else {
  175. reachabilityStatusString = (status == kFIRReachabilityNotReachable)
  176. ? kFIRReachabilityDisconnectedStatus
  177. : kFIRReachabilityConnectedStatus;
  178. }
  179. [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
  180. messageCode:kFIRNetworkMessageCodeReachabilityChecker004
  181. message:@"Network status has changed. Code, status"
  182. contexts:@[ @(status), reachabilityStatusString ]];
  183. reachabilityStatus_ = status;
  184. [reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_];
  185. }
  186. }
  187. @end
  188. static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
  189. SCNetworkReachabilityFlags flags, void *info) {
  190. FIRReachabilityChecker *checker = (__bridge FIRReachabilityChecker *)info;
  191. [checker reachabilityFlagsChanged:flags];
  192. }
  193. // This function used to be at the top of the file, but it was moved here
  194. // as a workaround for a suspected compiler bug. When compiled in Release mode
  195. // and run on an iOS device with WiFi disabled, the reachability code crashed
  196. // when calling SCNetworkReachabilityScheduleWithRunLoop, or shortly thereafter.
  197. // After unsuccessfully trying to diagnose the cause of the crash, it was
  198. // discovered that moving this function to the end of the file magically fixed
  199. // the crash. If you are going to edit this file, exercise caution and make sure
  200. // to test thoroughly with an iOS device under various network conditions.
  201. const NSString *FIRReachabilityStatusString(FIRReachabilityStatus status) {
  202. switch (status) {
  203. case kFIRReachabilityUnknown:
  204. return @"Reachability Unknown";
  205. case kFIRReachabilityNotReachable:
  206. return @"Not reachable";
  207. case kFIRReachabilityViaWifi:
  208. return @"Reachable via Wifi";
  209. case kFIRReachabilityViaCellular:
  210. return @"Reachable via Cellular Data";
  211. default:
  212. return [NSString stringWithFormat:@"Invalid reachability status %d", (int)status];
  213. }
  214. }