| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 | /* * * Copyright 2016 gRPC authors. * * 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 "GRPCConnectivityMonitor.h"#pragma mark Flags@implementation GRPCReachabilityFlags {  SCNetworkReachabilityFlags _flags;}+ (instancetype)flagsWithFlags:(SCNetworkReachabilityFlags)flags {  return [[self alloc] initWithFlags:flags];}- (instancetype)initWithFlags:(SCNetworkReachabilityFlags)flags {  if ((self = [super init])) {    _flags = flags;  }  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"];  }   */  #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]);}@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;}+ (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);  GRPCConnectivityMonitor *returnValue = [[self alloc] initWithReachability:reachability];  if (reachability) {    CFRelease(reachability);  }  return returnValue;}- (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;    }  }];}- (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);}- (void)setQueue:(dispatch_queue_t)queue {  _queue = queue ?: dispatch_get_main_queue();}- (void)dealloc {  if (_reachabilityRef) {    [self stopListening];    CFRelease(_reachabilityRef);  }}@end
 |