//
|
// ZFReachabilityManager.m
|
// ZFPlayer
|
//
|
// Copyright (c) 2016年 任子丰 ( http://github.com/renzifeng )
|
//
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// of this software and associated documentation files (the "Software"), to deal
|
// in the Software without restriction, including without limitation the rights
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
// copies of the Software, and to permit persons to whom the Software is
|
// furnished to do so, subject to the following conditions:
|
//
|
// The above copyright notice and this permission notice shall be included in
|
// all copies or substantial portions of the Software.
|
//
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
// THE SOFTWARE.
|
|
#import "ZFReachabilityManager.h"
|
#if !TARGET_OS_WATCH
|
#import <netinet/in.h>
|
#import <netinet6/in6.h>
|
#import <arpa/inet.h>
|
#import <ifaddrs.h>
|
#import <netdb.h>
|
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
|
|
NSString * const ZFReachabilityDidChangeNotification = @"com.ZFPlayer.reachability.change";
|
NSString * const ZFReachabilityNotificationStatusItem = @"ZFNetworkingReachabilityNotificationStatusItem";
|
|
typedef void (^ZFReachabilityStatusBlock)(ZFReachabilityStatus status);
|
|
NSString * ZFStringFromNetworkReachabilityStatus(ZFReachabilityStatus status) {
|
switch (status) {
|
case ZFReachabilityStatusNotReachable:
|
return NSLocalizedStringFromTable(@"Not Reachable", @"ZFPlayer", nil);
|
case ZFReachabilityStatusReachableViaWiFi:
|
return NSLocalizedStringFromTable(@"Reachable via WiFi", @"ZFPlayer", nil);
|
case ZFReachabilityStatusReachableVia2G:
|
return NSLocalizedStringFromTable(@"Reachable via 2G", @"ZFPlayer", nil);
|
case ZFReachabilityStatusReachableVia3G:
|
return NSLocalizedStringFromTable(@"Reachable via 3G", @"ZFPlayer", nil);
|
case ZFReachabilityStatusReachableVia4G:
|
return NSLocalizedStringFromTable(@"Reachable via 4G", @"ZFPlayer", nil);
|
case ZFReachabilityStatusUnknown:
|
default:
|
return NSLocalizedStringFromTable(@"Unknown", @"ZFPlayer", nil);
|
}
|
}
|
|
static ZFReachabilityStatus ZFReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) {
|
BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0);
|
BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0);
|
BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0));
|
BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0);
|
BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction));
|
|
ZFReachabilityStatus status = ZFReachabilityStatusUnknown;
|
if (isNetworkReachable == NO) {
|
status = ZFReachabilityStatusNotReachable;
|
}
|
#if TARGET_OS_IPHONE
|
else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
|
CTTelephonyNetworkInfo * info = [[CTTelephonyNetworkInfo alloc] init];
|
NSString *currentRadioAccessTechnology = info.currentRadioAccessTechnology;
|
if (currentRadioAccessTechnology) {
|
if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyLTE]) {
|
status = ZFReachabilityStatusReachableVia4G;
|
} else if ([currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyEdge] || [currentRadioAccessTechnology isEqualToString:CTRadioAccessTechnologyGPRS]) {
|
status = ZFReachabilityStatusReachableVia2G;
|
} else {
|
status = ZFReachabilityStatusReachableVia3G;
|
}
|
}
|
}
|
#endif
|
else {
|
status = ZFReachabilityStatusReachableViaWiFi;
|
}
|
return status;
|
|
}
|
|
/**
|
* Queue a status change notification for the main thread.
|
*
|
* This is done to ensure that the notifications are received in the same order
|
* as they are sent. If notifications are sent directly, it is possible that
|
* a queued notification (for an earlier status condition) is processed after
|
* the later update, resulting in the listener being left in the wrong state.
|
*/
|
static void ZFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, ZFReachabilityStatusBlock block) {
|
ZFReachabilityStatus status = ZFReachabilityStatusForFlags(flags);
|
dispatch_async(dispatch_get_main_queue(), ^{
|
if (block) block(status);
|
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
|
NSDictionary *userInfo = @{ ZFReachabilityNotificationStatusItem: @(status) };
|
[notificationCenter postNotificationName:ZFReachabilityDidChangeNotification object:nil userInfo:userInfo];
|
});
|
}
|
|
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
|
ZFPostReachabilityStatusChange(flags, (__bridge ZFReachabilityStatusBlock)info);
|
}
|
|
|
static const void * ZFReachabilityRetainCallback(const void *info) {
|
return Block_copy(info);
|
}
|
|
static void ZFReachabilityReleaseCallback(const void *info) {
|
if (info) {
|
Block_release(info);
|
}
|
}
|
|
@interface ZFReachabilityManager ()
|
|
@property (readonly, nonatomic, assign) SCNetworkReachabilityRef networkReachability;
|
@property (readwrite, nonatomic, assign) ZFReachabilityStatus networkReachabilityStatus;
|
@property (readwrite, nonatomic, copy) ZFReachabilityStatusBlock networkReachabilityStatusBlock;
|
|
@end
|
|
@implementation ZFReachabilityManager
|
|
+ (instancetype)sharedManager {
|
static ZFReachabilityManager *_sharedManager = nil;
|
static dispatch_once_t onceToken;
|
dispatch_once(&onceToken, ^{
|
_sharedManager = [self manager];
|
});
|
return _sharedManager;
|
}
|
|
+ (instancetype)managerForDomain:(NSString *)domain {
|
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);
|
ZFReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
|
CFRelease(reachability);
|
return manager;
|
}
|
|
+ (instancetype)managerForAddress:(const void *)address {
|
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
|
ZFReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
|
CFRelease(reachability);
|
return manager;
|
}
|
|
+ (instancetype)manager {
|
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
|
struct sockaddr_in6 address;
|
bzero(&address, sizeof(address));
|
address.sin6_len = sizeof(address);
|
address.sin6_family = AF_INET6;
|
#else
|
struct sockaddr_in address;
|
bzero(&address, sizeof(address));
|
address.sin_len = sizeof(address);
|
address.sin_family = AF_INET;
|
#endif
|
return [self managerForAddress:&address];
|
}
|
|
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
|
self = [super init];
|
if (!self) {
|
return nil;
|
}
|
_networkReachability = CFRetain(reachability);
|
self.networkReachabilityStatus = ZFReachabilityStatusUnknown;
|
|
return self;
|
}
|
|
- (instancetype)init NS_UNAVAILABLE
|
{
|
return nil;
|
}
|
|
- (void)dealloc {
|
[self stopMonitoring];
|
if (_networkReachability != NULL) {
|
CFRelease(_networkReachability);
|
}
|
}
|
|
#pragma mark -
|
|
- (BOOL)isReachable {
|
return [self isReachableViaWWAN] || [self isReachableViaWiFi];
|
}
|
|
- (BOOL)isReachableViaWWAN {
|
return (self.networkReachabilityStatus == ZFReachabilityStatusReachableVia2G ||self.networkReachabilityStatus == ZFReachabilityStatusReachableVia3G || self.networkReachabilityStatus == ZFReachabilityStatusReachableVia4G);
|
}
|
|
- (BOOL)isReachableViaWiFi {
|
return self.networkReachabilityStatus == ZFReachabilityStatusReachableViaWiFi;
|
}
|
|
#pragma mark -
|
|
- (void)startMonitoring {
|
[self stopMonitoring];
|
if (!self.networkReachability) {
|
return;
|
}
|
|
__weak __typeof(self)weakSelf = self;
|
ZFReachabilityStatusBlock callback = ^(ZFReachabilityStatus status) {
|
__strong __typeof(weakSelf)strongSelf = weakSelf;
|
strongSelf.networkReachabilityStatus = status;
|
if (strongSelf.networkReachabilityStatusBlock) {
|
strongSelf.networkReachabilityStatusBlock(status);
|
}
|
};
|
|
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, ZFReachabilityRetainCallback, ZFReachabilityReleaseCallback, NULL};
|
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
|
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
|
SCNetworkReachabilityFlags flags;
|
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
|
ZFPostReachabilityStatusChange(flags, callback);
|
}
|
});
|
}
|
|
- (void)stopMonitoring {
|
if (!self.networkReachability) {
|
return;
|
}
|
|
SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
|
}
|
|
#pragma mark -
|
|
- (NSString *)localizedNetworkReachabilityStatusString {
|
return ZFStringFromNetworkReachabilityStatus(self.networkReachabilityStatus);
|
}
|
|
#pragma mark -
|
|
- (void)setReachabilityStatusChangeBlock:(void (^)(ZFReachabilityStatus status))block {
|
self.networkReachabilityStatusBlock = block;
|
}
|
|
#pragma mark - NSKeyValueObserving
|
|
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
|
if ([key isEqualToString:@"reachable"] || [key isEqualToString:@"reachableViaWWAN"] || [key isEqualToString:@"reachableViaWiFi"]) {
|
return [NSSet setWithObject:@"networkReachabilityStatus"];
|
}
|
return [super keyPathsForValuesAffectingValueForKey:key];
|
}
|
|
@end
|
#endif
|