diff options
author | 2017-05-15 12:27:07 -0700 | |
---|---|---|
committer | 2017-05-15 12:27:07 -0700 | |
commit | 98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch) | |
tree | 131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /Firebase/Core/FIRReachabilityChecker.m | |
parent | 32461366c9e204a527ca05e6e9b9404a2454ac51 (diff) |
Initial
Diffstat (limited to 'Firebase/Core/FIRReachabilityChecker.m')
-rw-r--r-- | Firebase/Core/FIRReachabilityChecker.m | 245 |
1 files changed, 245 insertions, 0 deletions
diff --git a/Firebase/Core/FIRReachabilityChecker.m b/Firebase/Core/FIRReachabilityChecker.m new file mode 100644 index 0000000..66b6547 --- /dev/null +++ b/Firebase/Core/FIRReachabilityChecker.m @@ -0,0 +1,245 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "Private/FIRReachabilityChecker.h" +#import "Private/FIRReachabilityChecker+Internal.h" + +#import "Private/FIRNetwork.h" +#import "Private/FIRNetworkMessageCode.h" +#import "Private/FIRLogger.h" + +static void ReachabilityCallback(SCNetworkReachabilityRef reachability, + SCNetworkReachabilityFlags flags, void *info); + +static const struct FIRReachabilityApi kFIRDefaultReachabilityApi = { + SCNetworkReachabilityCreateWithName, + SCNetworkReachabilitySetCallback, + SCNetworkReachabilityScheduleWithRunLoop, + SCNetworkReachabilityUnscheduleFromRunLoop, + CFRelease, +}; + +static NSString *const kFIRReachabilityUnknownStatus = @"Unknown"; +static NSString *const kFIRReachabilityConnectedStatus = @"Connected"; +static NSString *const kFIRReachabilityDisconnectedStatus = @"Disconnected"; + +@interface FIRReachabilityChecker () + +@property(nonatomic, assign) const struct FIRReachabilityApi *reachabilityApi; +@property(nonatomic, assign) FIRReachabilityStatus reachabilityStatus; +@property(nonatomic, copy) NSString *host; +@property(nonatomic, assign) SCNetworkReachabilityRef reachability; + +@end + +@implementation FIRReachabilityChecker + +@synthesize reachabilityApi = reachabilityApi_; +@synthesize reachability = reachability_; + +- (const struct FIRReachabilityApi *)reachabilityApi { + return reachabilityApi_; +} + +- (void)setReachabilityApi:(const struct FIRReachabilityApi *)reachabilityApi { + if (reachability_) { + NSString *message = @"Cannot change reachability API while reachability is running. " + @"Call stop first."; + [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError + messageCode:kFIRNetworkMessageCodeReachabilityChecker000 + message:message]; + return; + } + reachabilityApi_ = reachabilityApi; +} + +@synthesize reachabilityStatus = reachabilityStatus_; +@synthesize host = host_; +@synthesize reachabilityDelegate = reachabilityDelegate_; +@synthesize loggerDelegate = loggerDelegate_; + +- (BOOL)isActive { + return reachability_ != nil; +} + +- (void)setReachabilityDelegate:(id<FIRReachabilityDelegate>)reachabilityDelegate { + if (reachabilityDelegate && + (![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(FIRReachabilityDelegate)])) { + FIRLogError( + kFIRLoggerCore, + [NSString stringWithFormat:@"I-NET%06ld", + (long)kFIRNetworkMessageCodeReachabilityChecker005], + @"Reachability delegate doesn't conform to Reachability protocol."); + return; + } + reachabilityDelegate_ = reachabilityDelegate; +} + +- (void)setLoggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate { + if (loggerDelegate && + (![(NSObject *)loggerDelegate conformsToProtocol:@protocol(FIRNetworkLoggerDelegate)])) { + FIRLogError( + kFIRLoggerCore, + [NSString stringWithFormat:@"I-NET%06ld", + (long)kFIRNetworkMessageCodeReachabilityChecker006], + @"Reachability delegate doesn't conform to Logger protocol."); + return; + } + loggerDelegate_ = loggerDelegate; +} + +- (instancetype)initWithReachabilityDelegate:(id<FIRReachabilityDelegate>)reachabilityDelegate + loggerDelegate:(id<FIRNetworkLoggerDelegate>)loggerDelegate + withHost:(NSString *)host { + self = [super init]; + + [self setLoggerDelegate:loggerDelegate]; + + if (!host || !host.length) { + [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError + messageCode:kFIRNetworkMessageCodeReachabilityChecker001 + message:@"Invalid host specified"]; + return nil; + } + if (self) { + [self setReachabilityDelegate:reachabilityDelegate]; + reachabilityApi_ = &kFIRDefaultReachabilityApi; + reachabilityStatus_ = kFIRReachabilityUnknown; + host_ = [host copy]; + reachability_ = nil; + } + return self; +} + +- (void)dealloc { + reachabilityDelegate_ = nil; + loggerDelegate_ = nil; + [self stop]; +} + +- (BOOL)start { + if (!reachability_) { + reachability_ = reachabilityApi_->createWithNameFn(kCFAllocatorDefault, [host_ UTF8String]); + if (!reachability_) { + return NO; + } + SCNetworkReachabilityContext context = { + 0, /* version */ + (__bridge void *)(self), /* info (passed as last parameter to reachability callback) */ + NULL, /* retain */ + NULL, /* release */ + NULL /* copyDescription */ + }; + if (!reachabilityApi_->setCallbackFn(reachability_, ReachabilityCallback, &context) || + !reachabilityApi_->scheduleWithRunLoopFn(reachability_, CFRunLoopGetMain(), + kCFRunLoopCommonModes)) { + reachabilityApi_->releaseFn(reachability_); + reachability_ = nil; + [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError + messageCode:kFIRNetworkMessageCodeReachabilityChecker002 + message:@"Failed to start reachability handle"]; + return NO; + } + } + [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug + messageCode:kFIRNetworkMessageCodeReachabilityChecker003 + message:@"Monitoring the network status"]; + return YES; +} + +- (void)stop { + if (reachability_) { + reachabilityStatus_ = kFIRReachabilityUnknown; + reachabilityApi_->unscheduleFromRunLoopFn(reachability_, CFRunLoopGetMain(), + kCFRunLoopCommonModes); + reachabilityApi_->releaseFn(reachability_); + reachability_ = nil; + } +} + +- (FIRReachabilityStatus)statusForFlags:(SCNetworkReachabilityFlags)flags { + FIRReachabilityStatus status = kFIRReachabilityNotReachable; + // If the Reachable flag is not set, we definitely don't have connectivity. + if (flags & kSCNetworkReachabilityFlagsReachable) { + // Reachable flag is set. Check further flags. + if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) { + // Connection required flag is not set, so we have connectivity. + status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular + : kFIRReachabilityViaWifi; + } else if ((flags & (kSCNetworkReachabilityFlagsConnectionOnDemand | + kSCNetworkReachabilityFlagsConnectionOnTraffic)) && + !(flags & kSCNetworkReachabilityFlagsInterventionRequired)) { + // If the connection on demand or connection on traffic flag is set, and user intervention + // is not required, we have connectivity. + status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular + : kFIRReachabilityViaWifi; + } + } + return status; +} + +- (void)reachabilityFlagsChanged:(SCNetworkReachabilityFlags)flags { + FIRReachabilityStatus status = [self statusForFlags:flags]; + if (reachabilityStatus_ != status) { + NSString *reachabilityStatusString; + if (status == kFIRReachabilityUnknown) { + reachabilityStatusString = kFIRReachabilityUnknownStatus; + } else { + reachabilityStatusString = (status == kFIRReachabilityNotReachable) + ? kFIRReachabilityDisconnectedStatus + : kFIRReachabilityConnectedStatus; + } + [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug + messageCode:kFIRNetworkMessageCodeReachabilityChecker004 + message:@"Network status has changed. Code, status" + contexts:@[ @(status), reachabilityStatusString ]]; + reachabilityStatus_ = status; + [reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_]; + } +} + +@end + +static void ReachabilityCallback(SCNetworkReachabilityRef reachability, + SCNetworkReachabilityFlags flags, void *info) { + FIRReachabilityChecker *checker = (__bridge FIRReachabilityChecker *)info; + [checker reachabilityFlagsChanged:flags]; +} + +// This function used to be at the top of the file, but it was moved here +// as a workaround for a suspected compiler bug. When compiled in Release mode +// and run on an iOS device with WiFi disabled, the reachability code crashed +// when calling SCNetworkReachabilityScheduleWithRunLoop, or shortly thereafter. +// After unsuccessfully trying to diagnose the cause of the crash, it was +// discovered that moving this function to the end of the file magically fixed +// the crash. If you are going to edit this file, exercise caution and make sure +// to test thoroughly with an iOS device under various network conditions. +const NSString *FIRReachabilityStatusString(FIRReachabilityStatus status) { + switch (status) { + case kFIRReachabilityUnknown: + return @"Reachability Unknown"; + + case kFIRReachabilityNotReachable: + return @"Not reachable"; + + case kFIRReachabilityViaWifi: + return @"Reachable via Wifi"; + + case kFIRReachabilityViaCellular: + return @"Reachable via Cellular Data"; + + default: + return [NSString stringWithFormat:@"Invalid reachability status %d", (int)status]; + } +} |