aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m
diff options
context:
space:
mode:
authorGravatar Muxi Yan <muxi@users.noreply.github.com>2018-02-28 16:04:29 -0800
committerGravatar GitHub <noreply@github.com>2018-02-28 16:04:29 -0800
commitc6c5ce923ca5233566dbba2550dc2876c5c2c4ef (patch)
treeac28cbf77b59f422bea9f4de8585887aff654cc3 /src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m
parentbaf1a412624d20d1c11f60ddd66d47e58b3e3f35 (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.m199
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