// AFNetworkReachabilityManager.m // Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // 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 "WFAFNetworkReachabilityManager.h" #if !TARGET_OS_WATCH #import #import #import #import #import NSString * const WFAFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change"; NSString * const WFAFNetworkingReachabilityNotificationStatusItem = @"AFNetworkingReachabilityNotificationStatusItem"; typedef void (^WFAFNetworkReachabilityStatusBlock)(WFAFNetworkReachabilityStatus status); NSString * WFAFStringFromNetworkReachabilityStatus(WFAFNetworkReachabilityStatus status) { switch (status) { case WFAFNetworkReachabilityStatusNotReachable: return NSLocalizedStringFromTable(@"Not Reachable", @"AFNetworking", nil); case WFAFNetworkReachabilityStatusReachableViaWWAN: return NSLocalizedStringFromTable(@"Reachable via WWAN", @"AFNetworking", nil); case WFAFNetworkReachabilityStatusReachableViaWiFi: return NSLocalizedStringFromTable(@"Reachable via WiFi", @"AFNetworking", nil); case WFAFNetworkReachabilityStatusUnknown: default: return NSLocalizedStringFromTable(@"Unknown", @"AFNetworking", nil); } } static WFAFNetworkReachabilityStatus WFAFNetworkReachabilityStatusForFlags(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)); WFAFNetworkReachabilityStatus status = WFAFNetworkReachabilityStatusUnknown; if (isNetworkReachable == NO) { status = WFAFNetworkReachabilityStatusNotReachable; } #if TARGET_OS_IPHONE else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) { status = WFAFNetworkReachabilityStatusReachableViaWWAN; } #endif else { status = WFAFNetworkReachabilityStatusReachableViaWiFi; } 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 WFAFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, WFAFNetworkReachabilityStatusBlock block) { WFAFNetworkReachabilityStatus status = WFAFNetworkReachabilityStatusForFlags(flags); dispatch_async(dispatch_get_main_queue(), ^{ if (block) { block(status); } NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; NSDictionary *userInfo = @{ WFAFNetworkingReachabilityNotificationStatusItem: @(status) }; [notificationCenter postNotificationName:WFAFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo]; }); } static void WFAFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { WFAFPostReachabilityStatusChange(flags, (__bridge WFAFNetworkReachabilityStatusBlock)info); } static const void * WFAFNetworkReachabilityRetainCallback(const void *info) { return Block_copy(info); } static void WFAFNetworkReachabilityReleaseCallback(const void *info) { if (info) { Block_release(info); } } @interface WFAFNetworkReachabilityManager () @property (readonly, nonatomic, assign) SCNetworkReachabilityRef networkReachability; @property (readwrite, nonatomic, assign) WFAFNetworkReachabilityStatus networkReachabilityStatus; @property (readwrite, nonatomic, copy) WFAFNetworkReachabilityStatusBlock networkReachabilityStatusBlock; @end @implementation WFAFNetworkReachabilityManager + (instancetype)sharedManager { static WFAFNetworkReachabilityManager *_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]); WFAFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; CFRelease(reachability); return manager; } + (instancetype)managerForAddress:(const void *)address { SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address); WFAFNetworkReachabilityManager *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 = WFAFNetworkReachabilityStatusUnknown; 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 == WFAFNetworkReachabilityStatusReachableViaWWAN; } - (BOOL)isReachableViaWiFi { return self.networkReachabilityStatus == WFAFNetworkReachabilityStatusReachableViaWiFi; } #pragma mark - - (void)startMonitoring { [self stopMonitoring]; if (!self.networkReachability) { return; } __weak __typeof(self)weakSelf = self; WFAFNetworkReachabilityStatusBlock callback = ^(WFAFNetworkReachabilityStatus status) { __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf.networkReachabilityStatus = status; if (strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); } }; SCNetworkReachabilityContext context = {0, (__bridge void *)callback, WFAFNetworkReachabilityRetainCallback, WFAFNetworkReachabilityReleaseCallback, NULL}; SCNetworkReachabilitySetCallback(self.networkReachability, WFAFNetworkReachabilityCallback, &context); SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{ SCNetworkReachabilityFlags flags; if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) { WFAFPostReachabilityStatusChange(flags, callback); } }); } - (void)stopMonitoring { if (!self.networkReachability) { return; } SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); } #pragma mark - - (NSString *)localizedNetworkReachabilityStatusString { return WFAFStringFromNetworkReachabilityStatus(self.networkReachabilityStatus); } #pragma mark - - (void)setReachabilityStatusChangeBlock:(void (^)(WFAFNetworkReachabilityStatus 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