AutorÃa | Ultima modificación | Ver Log |
// 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 <Foundation/Foundation.h>
#import "GoogleUtilities/Reachability/Public/GoogleUtilities/GULReachabilityChecker.h"
#import "GoogleUtilities/Reachability/GULReachabilityChecker+Internal.h"
#import "GoogleUtilities/Reachability/GULReachabilityMessageCode.h"
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"
static GULLoggerService kGULLoggerReachability = @"[GULReachability]";
#if !TARGET_OS_WATCH
static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
SCNetworkReachabilityFlags flags,
void *info);
static const struct GULReachabilityApi kGULDefaultReachabilityApi = {
SCNetworkReachabilityCreateWithName,
SCNetworkReachabilitySetCallback,
SCNetworkReachabilityScheduleWithRunLoop,
SCNetworkReachabilityUnscheduleFromRunLoop,
CFRelease,
};
static NSString *const kGULReachabilityUnknownStatus = @"Unknown";
static NSString *const kGULReachabilityConnectedStatus = @"Connected";
static NSString *const kGULReachabilityDisconnectedStatus = @"Disconnected";
#endif
@interface GULReachabilityChecker ()
@property(nonatomic, assign) const struct GULReachabilityApi *reachabilityApi;
@property(nonatomic, assign) GULReachabilityStatus reachabilityStatus;
@property(nonatomic, copy) NSString *host;
#if !TARGET_OS_WATCH
@property(nonatomic, assign) SCNetworkReachabilityRef reachability;
#endif
@end
@implementation GULReachabilityChecker
@synthesize reachabilityApi = reachabilityApi_;
#if !TARGET_OS_WATCH
@synthesize reachability = reachability_;
#endif
- (const struct GULReachabilityApi *)reachabilityApi {
return reachabilityApi_;
}
- (void)setReachabilityApi:(const struct GULReachabilityApi *)reachabilityApi {
#if !TARGET_OS_WATCH
if (reachability_) {
GULLogError(kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode000],
@"Cannot change reachability API while reachability is running. "
@"Call stop first.");
return;
}
reachabilityApi_ = reachabilityApi;
#endif
}
@synthesize reachabilityStatus = reachabilityStatus_;
@synthesize host = host_;
@synthesize reachabilityDelegate = reachabilityDelegate_;
- (BOOL)isActive {
#if !TARGET_OS_WATCH
return reachability_ != nil;
#else
return NO;
#endif
}
- (void)setReachabilityDelegate:(id<GULReachabilityDelegate>)reachabilityDelegate {
if (reachabilityDelegate &&
(![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(GULReachabilityDelegate)])) {
GULLogError(kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-NET%06ld", (long)kGULReachabilityMessageCode005],
@"Reachability delegate doesn't conform to Reachability protocol.");
return;
}
reachabilityDelegate_ = reachabilityDelegate;
}
- (instancetype)initWithReachabilityDelegate:(id<GULReachabilityDelegate>)reachabilityDelegate
withHost:(NSString *)host {
self = [super init];
if (!host || !host.length) {
GULLogError(kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode001],
@"Invalid host specified");
return nil;
}
if (self) {
#if !TARGET_OS_WATCH
[self setReachabilityDelegate:reachabilityDelegate];
reachabilityApi_ = &kGULDefaultReachabilityApi;
reachabilityStatus_ = kGULReachabilityUnknown;
host_ = [host copy];
reachability_ = nil;
#endif
}
return self;
}
- (void)dealloc {
reachabilityDelegate_ = nil;
[self stop];
}
- (BOOL)start {
#if TARGET_OS_WATCH
return NO;
#else
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;
GULLogError(kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode002],
@"Failed to start reachability handle");
return NO;
}
}
GULLogDebug(kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode003],
@"Monitoring the network status");
return YES;
#endif
}
- (void)stop {
#if !TARGET_OS_WATCH
if (reachability_) {
reachabilityStatus_ = kGULReachabilityUnknown;
reachabilityApi_->unscheduleFromRunLoopFn(reachability_, CFRunLoopGetMain(),
kCFRunLoopCommonModes);
reachabilityApi_->releaseFn(reachability_);
reachability_ = nil;
}
#endif
}
#if !TARGET_OS_WATCH
- (GULReachabilityStatus)statusForFlags:(SCNetworkReachabilityFlags)flags {
GULReachabilityStatus status = kGULReachabilityNotReachable;
// 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.
#if TARGET_OS_IOS || TARGET_OS_TV
status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular
: kGULReachabilityViaWifi;
#elif TARGET_OS_OSX
status = kGULReachabilityViaWifi;
#endif
} 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.
#if TARGET_OS_IOS || TARGET_OS_TV
status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular
: kGULReachabilityViaWifi;
#elif TARGET_OS_OSX
status = kGULReachabilityViaWifi;
#endif
}
}
return status;
}
- (void)reachabilityFlagsChanged:(SCNetworkReachabilityFlags)flags {
GULReachabilityStatus status = [self statusForFlags:flags];
if (reachabilityStatus_ != status) {
NSString *reachabilityStatusString;
if (status == kGULReachabilityUnknown) {
reachabilityStatusString = kGULReachabilityUnknownStatus;
} else {
reachabilityStatusString = (status == kGULReachabilityNotReachable)
? kGULReachabilityDisconnectedStatus
: kGULReachabilityConnectedStatus;
}
GULLogDebug(kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode004],
@"Network status has changed. Code:%@, status:%@", @(status),
reachabilityStatusString);
reachabilityStatus_ = status;
[reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_];
}
}
#endif
@end
#if !TARGET_OS_WATCH
static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
SCNetworkReachabilityFlags flags,
void *info) {
GULReachabilityChecker *checker = (__bridge GULReachabilityChecker *)info;
[checker reachabilityFlagsChanged:flags];
}
#endif
// 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 *GULReachabilityStatusString(GULReachabilityStatus status) {
switch (status) {
case kGULReachabilityUnknown:
return @"Reachability Unknown";
case kGULReachabilityNotReachable:
return @"Not reachable";
case kGULReachabilityViaWifi:
return @"Reachable via Wifi";
case kGULReachabilityViaCellular:
return @"Reachable via Cellular Data";
default:
return [NSString stringWithFormat:@"Invalid reachability status %d", (int)status];
}
}