diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m index 43204345f55a80669312c9aed48dbb708b50b84a..bbfb5aa45f36229ce75c3c04b0ff1b465cee8171 100644 --- a/src/objective-c/GRPCClient/GRPCCall.m +++ b/src/objective-c/GRPCClient/GRPCCall.m @@ -375,20 +375,6 @@ static NSMutableDictionary *callFlags; _state = GRXWriterStateStarted; } - // Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled). - // This makes RPCs in which the call isn't externally retained possible (as long as it is started - // before being autoreleased). - // Care is taken not to retain self strongly in any of the blocks used in this implementation, so - // that the life of the instance is determined by this retain cycle. - _retainSelf = self; - - _responseWriteable = [[GRXConcurrentWriteable alloc] initWithWriteable:writeable]; - - _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host path:_path]; - NSAssert(_wrappedCall, @"Error allocating RPC objects. Low memory?"); - - [self sendHeaders:_requestHeaders]; - [self invokeCall]; // TODO(jcanizales): Extract this logic somewhere common. NSString *host = [NSURL URLWithString:[@"https://" stringByAppendingString:_host]].host; if (!host) { @@ -397,15 +383,35 @@ static NSMutableDictionary *callFlags; } __weak typeof(self) weakSelf = self; _connectivityMonitor = [GRPCConnectivityMonitor monitorWithHost:host]; - [_connectivityMonitor handleLossWithHandler:^{ + void (^handler)() = ^{ typeof(self) strongSelf = weakSelf; if (strongSelf) { [strongSelf finishWithError:[NSError errorWithDomain:kGRPCErrorDomain code:GRPCErrorCodeUnavailable userInfo:@{NSLocalizedDescriptionKey: @"Connectivity lost."}]]; - [[GRPCHost hostWithAddress:strongSelf->_host] disconnect]; } - }]; + }; + [_connectivityMonitor handleLossWithHandler:handler + wifiStatusChangeHandler:handler]; + + // Create a retain cycle so that this instance lives until the RPC finishes + // (or is cancelled). + // This makes RPCs in which the call isn't externally retained possible (as + // long as it is started + // before being autoreleased). + // Care is taken not to retain self strongly in any of the blocks used in this + // implementation, so + // that the life of the instance is determined by this retain cycle. + _retainSelf = self; + + _responseWriteable = + [[GRXConcurrentWriteable alloc] initWithWriteable:writeable]; + + _wrappedCall = [[GRPCWrappedCall alloc] initWithHost:_host path:_path]; + NSAssert(_wrappedCall, @"Error allocating RPC objects. Low memory?"); + + [self sendHeaders:_requestHeaders]; + [self invokeCall]; } - (void)setState:(GRXWriterState)newState { diff --git a/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.h b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.h index 2fae410331517e9739964683402706fd6f1e1e65..8d5c1b3e68aff30cba90aa374ab5d3c13705e39a 100644 --- a/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.h +++ b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.h @@ -73,5 +73,6 @@ * Only one handler is active at a time, so if this method is called again before the previous * handler has been called, it might never be called at all (or yes, if it has already been queued). */ -- (void)handleLossWithHandler:(nonnull void (^)())handler; +- (void)handleLossWithHandler:(void (^)())handler + wifiStatusChangeHandler:(void (^)())wifiStatusChangeHandler; @end diff --git a/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m index b4061bd5ef52e5100c75c00b15d948cadda1bbc3..d736c5e6b037e1582b64263f6bbd920c49967ef4 100644 --- a/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m +++ b/src/objective-c/GRPCClient/private/GRPCConnectivityMonitor.m @@ -120,6 +120,7 @@ static void PassFlagsToContextInfoBlock(SCNetworkReachabilityRef target, @implementation GRPCConnectivityMonitor { SCNetworkReachabilityRef _reachabilityRef; + GRPCReachabilityFlags *_previousReachabilityFlags; } - (nullable instancetype)initWithReachability:(nullable SCNetworkReachabilityRef)reachability { @@ -129,6 +130,13 @@ static void PassFlagsToContextInfoBlock(SCNetworkReachabilityRef target, if ((self = [super init])) { _reachabilityRef = CFRetain(reachability); _queue = dispatch_get_main_queue(); + SCNetworkReachabilityFlags flags; + if (SCNetworkReachabilityGetFlags(_reachabilityRef, &flags)) { + _previousReachabilityFlags = + [[GRPCReachabilityFlags alloc] initWithFlags:flags]; + } else { + _previousReachabilityFlags = 0; + } } return self; } @@ -149,11 +157,16 @@ static void PassFlagsToContextInfoBlock(SCNetworkReachabilityRef target, return returnValue; } -- (void)handleLossWithHandler:(void (^)())handler { +- (void)handleLossWithHandler:(void (^)())handler + wifiStatusChangeHandler:(nonnull void (^)())wifiStatusChangeHandler { [self startListeningWithHandler:^(GRPCReachabilityFlags *flags) { - if (!flags.isHostReachable) { + if (!flags.reachable && handler) { handler(); + } else if (flags.isWWAN ^ _previousReachabilityFlags.isWWAN && + wifiStatusChangeHandler) { + wifiStatusChangeHandler(); } + _previousReachabilityFlags = flags; }]; } diff --git a/src/objective-c/GRPCClient/private/GRPCHost.m b/src/objective-c/GRPCClient/private/GRPCHost.m index f8634b448e9d9ada50c4bb37fde26a90d16489ad..ee6045653e9433b57f41c544b563679f6aca3a4c 100644 --- a/src/objective-c/GRPCClient/private/GRPCHost.m +++ b/src/objective-c/GRPCClient/private/GRPCHost.m @@ -43,6 +43,7 @@ #import "GRPCChannel.h" #import "GRPCCompletionQueue.h" +#import "GRPCConnectivityMonitor.h" #import "NSDictionary+GRPC.h" NS_ASSUME_NONNULL_BEGIN @@ -52,6 +53,7 @@ NS_ASSUME_NONNULL_BEGIN #define GRPC_OBJC_VERSION_STRING @"1.0.0" static NSMutableDictionary *kHostCache; +static GRPCConnectivityMonitor *connectivityMonitor = nil; @implementation GRPCHost { // TODO(mlumish): Investigate whether caching channels with strong links is a good idea. @@ -98,6 +100,16 @@ static NSMutableDictionary *kHostCache; _address = address; _secure = YES; kHostCache[address] = self; + + if (!connectivityMonitor) { + connectivityMonitor = + [GRPCConnectivityMonitor monitorWithHost:hostURL.host]; + void (^handler)() = ^{ + [GRPCHost flushChannelCache]; + }; + [connectivityMonitor handleLossWithHandler:handler + wifiStatusChangeHandler:handler]; + } } } return self; @@ -110,6 +122,7 @@ static NSMutableDictionary *kHostCache; BOOL * _Nonnull stop) { [host disconnect]; }]; + connectivityMonitor = nil; } } diff --git a/src/objective-c/GRPCClient/private/GRPCReachabilityFlagNames.xmacro.h b/src/objective-c/GRPCClient/private/GRPCReachabilityFlagNames.xmacro.h index 4b92504b555378e1d1ac2d7fa5c403ab7036a838..1c907877a0e03d8eadf4a0a8dfe8047a5f3312af 100644 --- a/src/objective-c/GRPCClient/private/GRPCReachabilityFlagNames.xmacro.h +++ b/src/objective-c/GRPCClient/private/GRPCReachabilityFlagNames.xmacro.h @@ -65,3 +65,4 @@ GRPC_XMACRO_ITEM(interventionRequired, InterventionRequired) GRPC_XMACRO_ITEM(connectionOnDemand, ConnectionOnDemand) GRPC_XMACRO_ITEM(isLocalAddress, IsLocalAddress) GRPC_XMACRO_ITEM(isDirect, IsDirect) +GRPC_XMACRO_ITEM(isWWAN, IsWWAN) diff --git a/src/objective-c/tests/Connectivity/ConnectivityTestingApp.xcodeproj/project.pbxproj b/src/objective-c/tests/Connectivity/ConnectivityTestingApp.xcodeproj/project.pbxproj index 2a9466c03f11e8183720c6520d4919b174c51a66..3f26c98564863c193938fafb91cd5f59d81c6e34 100644 --- a/src/objective-c/tests/Connectivity/ConnectivityTestingApp.xcodeproj/project.pbxproj +++ b/src/objective-c/tests/Connectivity/ConnectivityTestingApp.xcodeproj/project.pbxproj @@ -156,7 +156,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 5347BF6C41E7888C1C05CD88 /* [CP] Copy Pods Resources */ = {