208 lines
6.8 KiB
Objective-C
208 lines
6.8 KiB
Objective-C
/*
|
|
*
|
|
* Copyright 2016, Google Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* * Neither the name of Google Inc. nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#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 (^)())lossHandler
|
|
wifiStatusChangeHandler:(nullable 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
|