|
|
|
@ -18,175 +18,74 @@ |
|
|
|
|
|
|
|
|
|
#import "GRPCConnectivityMonitor.h" |
|
|
|
|
|
|
|
|
|
#pragma mark Flags |
|
|
|
|
#include <netinet/in.h> |
|
|
|
|
|
|
|
|
|
@implementation GRPCReachabilityFlags { |
|
|
|
|
SCNetworkReachabilityFlags _flags; |
|
|
|
|
} |
|
|
|
|
NSString *kGRPCConnectivityNotification = @"kGRPCConnectivityNotification"; |
|
|
|
|
|
|
|
|
|
+ (instancetype)flagsWithFlags:(SCNetworkReachabilityFlags)flags { |
|
|
|
|
return [[self alloc] initWithFlags:flags]; |
|
|
|
|
} |
|
|
|
|
static SCNetworkReachabilityRef reachability; |
|
|
|
|
static GRPCConnectivityStatus currentStatus; |
|
|
|
|
|
|
|
|
|
- (instancetype)initWithFlags:(SCNetworkReachabilityFlags)flags { |
|
|
|
|
if ((self = [super init])) { |
|
|
|
|
_flags = flags; |
|
|
|
|
// Aggregate information in flags into network status. |
|
|
|
|
GRPCConnectivityStatus CalculateConnectivityStatus(SCNetworkReachabilityFlags flags) { |
|
|
|
|
GRPCConnectivityStatus result = GRPCConnectivityUnknown; |
|
|
|
|
if (((flags & kSCNetworkReachabilityFlagsReachable) == 0) || |
|
|
|
|
((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0)) { |
|
|
|
|
return GRPCConnectivityNoNetwork; |
|
|
|
|
} |
|
|
|
|
return self; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
* One accessor method implementation per flag. Example: |
|
|
|
|
|
|
|
|
|
- (BOOL)isCell { \ |
|
|
|
|
return !!(_flags & kSCNetworkReachabilityFlagsIsWWAN); \ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
|
#define GRPC_XMACRO_ITEM(methodName, FlagName) \ |
|
|
|
|
- (BOOL)methodName { \ |
|
|
|
|
return !!(_flags & kSCNetworkReachabilityFlags ## FlagName); \ |
|
|
|
|
} |
|
|
|
|
#include "GRPCReachabilityFlagNames.xmacro.h" |
|
|
|
|
#undef GRPC_XMACRO_ITEM |
|
|
|
|
|
|
|
|
|
- (BOOL)isHostReachable { |
|
|
|
|
// Note: connectionOnDemand means it'll be reachable only if using the CFSocketStream API or APIs |
|
|
|
|
// on top of it. |
|
|
|
|
// connectionRequired means we can't tell until a connection is attempted (e.g. for VPN on |
|
|
|
|
// demand). |
|
|
|
|
return self.reachable && !self.interventionRequired && !self.connectionOnDemand; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (NSString *)description { |
|
|
|
|
NSMutableArray *activeOptions = [NSMutableArray arrayWithCapacity:9]; |
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
* For each flag, add its name to the array if it's ON. Example: |
|
|
|
|
|
|
|
|
|
if (self.isCell) { |
|
|
|
|
[activeOptions addObject:@"isCell"]; |
|
|
|
|
result = GRPCConnectivityWiFi; |
|
|
|
|
#if TARGET_OS_IPHONE |
|
|
|
|
if (flags & kSCNetworkReachabilityFlagsIsWWAN) { |
|
|
|
|
return result = GRPCConnectivityCellular; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
|
#define GRPC_XMACRO_ITEM(methodName, FlagName) \ |
|
|
|
|
if (self.methodName) { \ |
|
|
|
|
[activeOptions addObject:@ #methodName]; \ |
|
|
|
|
} |
|
|
|
|
#include "GRPCReachabilityFlagNames.xmacro.h" |
|
|
|
|
#undef GRPC_XMACRO_ITEM |
|
|
|
|
|
|
|
|
|
return activeOptions.count == 0 ? @"(none)" : [activeOptions componentsJoinedByString:@", "]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (BOOL)isEqual:(id)object { |
|
|
|
|
return [object isKindOfClass:[GRPCReachabilityFlags class]] && |
|
|
|
|
_flags == ((GRPCReachabilityFlags *)object)->_flags; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (NSUInteger)hash { |
|
|
|
|
return _flags; |
|
|
|
|
} |
|
|
|
|
@end |
|
|
|
|
|
|
|
|
|
#pragma mark Connectivity Monitor |
|
|
|
|
|
|
|
|
|
// Assumes the third argument is a block that accepts a GRPCReachabilityFlags object, and passes the |
|
|
|
|
// received ones to it. |
|
|
|
|
static void PassFlagsToContextInfoBlock(SCNetworkReachabilityRef target, |
|
|
|
|
SCNetworkReachabilityFlags flags, |
|
|
|
|
void *info) { |
|
|
|
|
#pragma unused (target) |
|
|
|
|
// This can be called many times with the same info. The info is retained by SCNetworkReachability |
|
|
|
|
// while this function is being executed. |
|
|
|
|
void (^handler)(GRPCReachabilityFlags *) = (__bridge void (^)(GRPCReachabilityFlags *))info; |
|
|
|
|
handler([[GRPCReachabilityFlags alloc] initWithFlags:flags]); |
|
|
|
|
#endif |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@implementation GRPCConnectivityMonitor { |
|
|
|
|
SCNetworkReachabilityRef _reachabilityRef; |
|
|
|
|
GRPCReachabilityFlags *_previousReachabilityFlags; |
|
|
|
|
} |
|
|
|
|
static void ReachabilityCallback( |
|
|
|
|
SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) { |
|
|
|
|
GRPCConnectivityStatus newStatus = CalculateConnectivityStatus(flags); |
|
|
|
|
|
|
|
|
|
- (nullable instancetype)initWithReachability:(nullable SCNetworkReachabilityRef)reachability { |
|
|
|
|
if (!reachability) { |
|
|
|
|
return nil; |
|
|
|
|
if (newStatus != currentStatus) { |
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:kGRPCConnectivityNotification |
|
|
|
|
object:nil]; |
|
|
|
|
currentStatus = newStatus; |
|
|
|
|
} |
|
|
|
|
if ((self = [super init])) { |
|
|
|
|
_reachabilityRef = CFRetain(reachability); |
|
|
|
|
_queue = dispatch_get_main_queue(); |
|
|
|
|
_previousReachabilityFlags = nil; |
|
|
|
|
} |
|
|
|
|
return self; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
+ (nullable instancetype)monitorWithHost:(nonnull NSString *)host { |
|
|
|
|
const char *hostName = host.UTF8String; |
|
|
|
|
if (!hostName) { |
|
|
|
|
[NSException raise:NSInvalidArgumentException |
|
|
|
|
format:@"host.UTF8String returns NULL for %@", host]; |
|
|
|
|
} |
|
|
|
|
SCNetworkReachabilityRef reachability = |
|
|
|
|
SCNetworkReachabilityCreateWithName(NULL, hostName); |
|
|
|
|
@implementation GRPCConnectivityMonitor |
|
|
|
|
|
|
|
|
|
GRPCConnectivityMonitor *returnValue = [[self alloc] initWithReachability:reachability]; |
|
|
|
|
if (reachability) { |
|
|
|
|
CFRelease(reachability); |
|
|
|
|
} |
|
|
|
|
return returnValue; |
|
|
|
|
} |
|
|
|
|
+ (void)initialize { |
|
|
|
|
if (self == [GRPCConnectivityMonitor self]) { |
|
|
|
|
struct sockaddr_in addr = {0}; |
|
|
|
|
addr.sin_len = sizeof(addr); |
|
|
|
|
addr.sin_family = AF_INET; |
|
|
|
|
reachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&addr); |
|
|
|
|
currentStatus = GRPCConnectivityUnknown; |
|
|
|
|
|
|
|
|
|
- (void)handleLossWithHandler:(nullable void (^)(void))lossHandler |
|
|
|
|
wifiStatusChangeHandler:(nullable void (^)(void))wifiStatusChangeHandler { |
|
|
|
|
__weak typeof(self) weakSelf = self; |
|
|
|
|
[self startListeningWithHandler:^(GRPCReachabilityFlags *flags) { |
|
|
|
|
typeof(self) strongSelf = weakSelf; |
|
|
|
|
if (strongSelf) { |
|
|
|
|
if (lossHandler && !flags.reachable) { |
|
|
|
|
lossHandler(); |
|
|
|
|
#if TARGET_OS_IPHONE |
|
|
|
|
} else if (wifiStatusChangeHandler && |
|
|
|
|
strongSelf->_previousReachabilityFlags && |
|
|
|
|
(flags.isWWAN ^ |
|
|
|
|
strongSelf->_previousReachabilityFlags.isWWAN)) { |
|
|
|
|
wifiStatusChangeHandler(); |
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
strongSelf->_previousReachabilityFlags = flags; |
|
|
|
|
SCNetworkConnectionFlags flags; |
|
|
|
|
if (SCNetworkReachabilityGetFlags(reachability, &flags)) { |
|
|
|
|
currentStatus = CalculateConnectivityStatus(flags); |
|
|
|
|
} |
|
|
|
|
}]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)startListeningWithHandler:(void (^)(GRPCReachabilityFlags *))handler { |
|
|
|
|
// Copy to ensure the handler block is in the heap (and so can't be deallocated when this method |
|
|
|
|
// returns). |
|
|
|
|
void (^copiedHandler)(GRPCReachabilityFlags *) = [handler copy]; |
|
|
|
|
SCNetworkReachabilityContext context = { |
|
|
|
|
.version = 0, |
|
|
|
|
.info = (__bridge void *)copiedHandler, |
|
|
|
|
.retain = CFRetain, |
|
|
|
|
.release = CFRelease, |
|
|
|
|
}; |
|
|
|
|
// The following will retain context.info, and release it when the callback is set to NULL. |
|
|
|
|
SCNetworkReachabilitySetCallback(_reachabilityRef, PassFlagsToContextInfoBlock, &context); |
|
|
|
|
SCNetworkReachabilitySetDispatchQueue(_reachabilityRef, _queue); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)stopListening { |
|
|
|
|
// This releases the block on context.info. |
|
|
|
|
SCNetworkReachabilitySetCallback(_reachabilityRef, NULL, NULL); |
|
|
|
|
SCNetworkReachabilitySetDispatchQueue(_reachabilityRef, NULL); |
|
|
|
|
SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; |
|
|
|
|
if (!SCNetworkReachabilitySetCallback(reachability, ReachabilityCallback, &context) || |
|
|
|
|
!SCNetworkReachabilityScheduleWithRunLoop( |
|
|
|
|
reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes)) { |
|
|
|
|
NSLog(@"gRPC connectivity monitor fail to set"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)setQueue:(dispatch_queue_t)queue { |
|
|
|
|
_queue = queue ?: dispatch_get_main_queue(); |
|
|
|
|
+ (void)registerObserver:(_Nonnull id)observer |
|
|
|
|
selector:(SEL)selector { |
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:observer |
|
|
|
|
selector:selector |
|
|
|
|
name:kGRPCConnectivityNotification |
|
|
|
|
object:nil]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
- (void)dealloc { |
|
|
|
|
if (_reachabilityRef) { |
|
|
|
|
[self stopListening]; |
|
|
|
|
CFRelease(_reachabilityRef); |
|
|
|
|
} |
|
|
|
|
+ (void)unregisterObserver:(_Nonnull id)observer { |
|
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:observer]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@end |
|
|
|
|