diff options
author | Muxi Yan <muxi@users.noreply.github.com> | 2018-02-28 16:04:29 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-28 16:04:29 -0800 |
commit | c6c5ce923ca5233566dbba2550dc2876c5c2c4ef (patch) | |
tree | ac28cbf77b59f422bea9f4de8585887aff654cc3 /src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m | |
parent | baf1a412624d20d1c11f60ddd66d47e58b3e3f35 (diff) |
Revert "Refactor connectivity monitor on iOS"
Diffstat (limited to 'src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m')
-rw-r--r-- | src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m | 199 |
1 files changed, 150 insertions, 49 deletions
diff --git a/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m index 7f31c7e23e..c8e10dd75f 100644 --- a/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m +++ b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m @@ -18,74 +18,175 @@ #import "GRPCConnectivityMonitor.h" -#include <netinet/in.h> +#pragma mark Flags -NSString *kGRPCConnectivityNotification = @"kGRPCConnectivityNotification"; +@implementation GRPCReachabilityFlags { + SCNetworkReachabilityFlags _flags; +} -static SCNetworkReachabilityRef reachability; -static GRPCConnectivityStatus currentStatus; ++ (instancetype)flagsWithFlags:(SCNetworkReachabilityFlags)flags { + return [[self alloc] initWithFlags:flags]; +} -// Aggregate information in flags into network status. -GRPCConnectivityStatus CalculateConnectivityStatus(SCNetworkReachabilityFlags flags) { - GRPCConnectivityStatus result = GRPCConnectivityUnknown; - if (((flags & kSCNetworkReachabilityFlagsReachable) == 0) || - ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0)) { - return GRPCConnectivityNoNetwork; +- (instancetype)initWithFlags:(SCNetworkReachabilityFlags)flags { + if ((self = [super init])) { + _flags = flags; } - result = GRPCConnectivityWiFi; -#if TARGET_OS_IPHONE - if (flags & kSCNetworkReachabilityFlagsIsWWAN) { - return result = GRPCConnectivityCellular; + 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"]; } -#endif - return result; + + */ + #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:@", "]; } -static void ReachabilityCallback( - SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) { - GRPCConnectivityStatus newStatus = CalculateConnectivityStatus(flags); +- (BOOL)isEqual:(id)object { + return [object isKindOfClass:[GRPCReachabilityFlags class]] && + _flags == ((GRPCReachabilityFlags *)object)->_flags; +} - if (newStatus != currentStatus) { - [[NSNotificationCenter defaultCenter] postNotificationName:kGRPCConnectivityNotification - object:nil]; - currentStatus = newStatus; +- (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]); +} + +@implementation GRPCConnectivityMonitor { + SCNetworkReachabilityRef _reachabilityRef; + GRPCReachabilityFlags *_previousReachabilityFlags; +} + +- (nullable instancetype)initWithReachability:(nullable SCNetworkReachabilityRef)reachability { + if (!reachability) { + return nil; } + if ((self = [super init])) { + _reachabilityRef = CFRetain(reachability); + _queue = dispatch_get_main_queue(); + _previousReachabilityFlags = nil; + } + return self; } -@implementation GRPCConnectivityMonitor ++ (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); -+ (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; + GRPCConnectivityMonitor *returnValue = [[self alloc] initWithReachability:reachability]; + if (reachability) { + CFRelease(reachability); + } + return returnValue; +} - SCNetworkConnectionFlags flags; - if (SCNetworkReachabilityGetFlags(reachability, &flags)) { - currentStatus = CalculateConnectivityStatus(flags); +- (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; } + }]; +} - 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)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)registerObserver:(_Nonnull id)observer - selector:(SEL)selector { - [[NSNotificationCenter defaultCenter] addObserver:observer - selector:selector - name:kGRPCConnectivityNotification - object:nil]; +- (void)stopListening { + // This releases the block on context.info. + SCNetworkReachabilitySetCallback(_reachabilityRef, NULL, NULL); + SCNetworkReachabilitySetDispatchQueue(_reachabilityRef, NULL); } -+ (void)unregisterObserver:(_Nonnull id)observer { - [[NSNotificationCenter defaultCenter] removeObserver:observer]; +- (void)setQueue:(dispatch_queue_t)queue { + _queue = queue ?: dispatch_get_main_queue(); +} + +- (void)dealloc { + if (_reachabilityRef) { + [self stopListening]; + CFRelease(_reachabilityRef); + } } @end |