原始碼閱讀:AFNetworking(五)——AFNetworkReachabilityManager

堯少羽發表於2018-02-27

該文章閱讀的AFNetworking的版本為3.2.0。

該類主要是用來檢測網路環境的變化。

1.介面檔案

1.1.列舉

typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
    AFNetworkReachabilityStatusUnknown          = -1,
    AFNetworkReachabilityStatusNotReachable     = 0,
    AFNetworkReachabilityStatusReachableViaWWAN = 1,
    AFNetworkReachabilityStatusReachableViaWiFi = 2,
};
複製程式碼

這個列舉提供了可以監控的網路狀態:

AFNetworkReachabilityStatusUnknown 網路狀態未知 AFNetworkReachabilityStatusNotReachable 網路狀態無連線 AFNetworkReachabilityStatusReachableViaWWAN 網路狀態為蜂窩行動網路連線 AFNetworkReachabilityStatusReachableViaWiFi 網路狀態為無線區域網連線

1.2.屬性

/**
 當前網路連線狀態
 */
@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;

/**
 當前是否有網路連線
 */
@property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;

/**
 當前網路連線狀態是否為蜂窩行動網路連線
 */
@property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;

/**
 當前網路連線狀態是否為無線區域網連線
 */
@property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;
複製程式碼

1.3.方法

/**
 獲取單例物件
 */
+ (instancetype)sharedManager;

/**
 例項化預設socket地址的物件,並主動監視預設socket地址的狀態
 */
+ (instancetype)manager;

/**
 例項化指定域的物件,並主動監視指定域的狀態
 */
+ (instancetype)managerForDomain:(NSString *)domain;

/**
 例項化指定socket地址的物件,並主動監視指定socket地址的狀態
 */
+ (instancetype)managerForAddress:(const void *)address;

/**
 以指定SCNetworkReachabilityRef物件進行初始化,並主動監視指定SCNetworkReachabilityRef物件的狀態
 */
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;

/**
 初始化方法,這個方法被手動禁止了,如果強制呼叫這個方法就會報錯
 */
- (nullable instancetype)init NS_UNAVAILABLE;

/**
 開始監控網路狀態
 */
- (void)startMonitoring;

/**
 停止監控網路狀態
 */
- (void)stopMonitoring;

/**
 獲得網路狀態的本地文字描述
 */
- (NSString *)localizedNetworkReachabilityStatusString;

/**
 當網路狀態發生變化時,會回撥block
 */
- (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block;
複製程式碼

1.4.全域性靜態常量

FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;
複製程式碼

這兩個常量是用在通過通知來監聽網路狀態變化。當需要用通知來監聽網路狀態變化時,先監聽AFNetworkingReachabilityDidChangeNotification通知,然後在通知呼叫的方法中獲取傳遞過來引數中的屬性userInfo,這個屬性是NSDictionary型別的,通過key值AFNetworkingReachabilityNotificationStatusItem就可以獲得當前的網路狀態。

1.5.全域性方法

FOUNDATION_EXPORT NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status);
複製程式碼

這個方法的作用是將傳入的網路狀態列舉值轉成本地文字描述。

2.實現檔案

2.1.全域性靜態常量

NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change";
NSString * const AFNetworkingReachabilityNotificationStatusItem = @"AFNetworkingReachabilityNotificationStatusItem";
複製程式碼

.h檔案中全域性靜態常量賦值

2.2.別名

typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status);
複製程式碼

為引數為AFNetworkReachabilityStatus型別無返回值的block起名為AFNetworkReachabilityStatusBlock

2.3.私有方法

/**
 這個方法就是.h檔案最後一個方法的實現,用來將傳入的網路狀態列舉值轉成本地文字描述
 */
NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status) {
    // 根據不同的列舉值返回相對應的文字,並進行本地化處理
    switch (status) {
        case AFNetworkReachabilityStatusNotReachable:
            return NSLocalizedStringFromTable(@"Not Reachable", @"AFNetworking", nil);
        case AFNetworkReachabilityStatusReachableViaWWAN:
            return NSLocalizedStringFromTable(@"Reachable via WWAN", @"AFNetworking", nil);
        case AFNetworkReachabilityStatusReachableViaWiFi:
            return NSLocalizedStringFromTable(@"Reachable via WiFi", @"AFNetworking", nil);
        case AFNetworkReachabilityStatusUnknown:
        default:
            return NSLocalizedStringFromTable(@"Unknown", @"AFNetworking", nil);
    }
}

/**
 該方法是將SCNetworkReachabilityFlags型別的列舉轉換成AFNetworking自定義的網路狀態列舉
 */
static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(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));

    // 根據不同情況轉換成對應的網路狀態列舉值
    AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown;
    if (isNetworkReachable == NO) {
        status = AFNetworkReachabilityStatusNotReachable;
    }
#if	TARGET_OS_IPHONE
    else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
        status = AFNetworkReachabilityStatusReachableViaWWAN;
    }
#endif
    else {
        status = AFNetworkReachabilityStatusReachableViaWiFi;
    }

    return status;
}

/**
 為了保證網路狀態變化時block回撥和傳送通知的統一性,將兩者在主執行緒統一呼叫
 */
static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
    // 將flags轉成自定義網路狀態列舉值
    AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
    // 主佇列非同步
    dispatch_async(dispatch_get_main_queue(), ^{
        // 回撥block
        if (block) {
            block(status);
        }
        // 傳送通知
        NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
        NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
        [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
    });
}

/**
 將上面的方法封裝,作為引數傳入網路狀態監控回撥方法SCNetworkReachabilitySetCallback中
 */
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
    AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}

/**
 將block複製到堆中,並封裝成方法作為引數構建SCNetworkReachabilityContext結構體
 */
static const void * AFNetworkReachabilityRetainCallback(const void *info) {
    return Block_copy(info);
}

/**
 釋放複製到堆中的block,並封裝成方法作為引數構建SCNetworkReachabilityContext結構體
 */
static void AFNetworkReachabilityReleaseCallback(const void *info) {
    if (info) {
        Block_release(info);
    }
}
複製程式碼

2.4.類擴充套件

/**
 儲存傳入的SCNetworkReachabilityRef物件
 */
@property (readonly, nonatomic, assign) SCNetworkReachabilityRef networkReachability;

/**
 儲存當前網路狀態的列舉值
 */
@property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;

/**
 儲存傳入的用於回撥的block
 */
@property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock;
複製程式碼

2.5.方法實現

+ (instancetype)sharedManager {
    // 呼叫以預設socket地址例項化物件的方法,生成單例物件
    static AFNetworkReachabilityManager *_sharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedManager = [self manager];
    });

    return _sharedManager;
}

+ (instancetype)managerForDomain:(NSString *)domain {
    // 利用傳入的域生成SCNetworkReachabilityRef物件
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);
    
    // 利用SCNetworkReachabilityRef物件例項化AFNetworkReachabilityManager物件
    AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
    
    // 釋放SCNetworkReachabilityRef物件
    CFRelease(reachability);

    return manager;
}

+ (instancetype)managerForAddress:(const void *)address {
    // 利用傳入的地址象例項化AFNetworkReachabilityManager物件
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
    
    // 利用SCNetworkReachabilityRef物件例項化AFNetworkReachabilityManager物件
    AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];

    // 釋放SCNetworkReachabilityRef物件
    CFRelease(reachability);
    
    return manager;
}

+ (instancetype)manager
{
    // 根據版本號適配IPv6和IPv4,獲得預設地址
#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 = AFNetworkReachabilityStatusUnknown;

    return self;
}

- (instancetype)init NS_UNAVAILABLE
{
    return nil;
}

- (void)dealloc {
    // 停止監控
    [self stopMonitoring];
    
    // 釋放SCNetworkReachabilityRef物件
    if (_networkReachability != NULL) {
        CFRelease(_networkReachability);
    }
}

- (BOOL)isReachable {
    return [self isReachableViaWWAN] || [self isReachableViaWiFi];
}

- (BOOL)isReachableViaWWAN {
    return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWWAN;
}

- (BOOL)isReachableViaWiFi {
    return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWiFi;
}

- (void)startMonitoring {
    // 先停止之前的網路監控
    [self stopMonitoring];

    // 如果沒有傳入或者生成SCNetworkReachabilityRef物件就直接返回
    if (!self.networkReachability) {
        return;
    }

    // 構造網路監控所需的引數
    __weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        
        // 回撥網路狀態
        strongSelf.networkReachabilityStatus = status;
        if (strongSelf.networkReachabilityStatusBlock) {
            strongSelf.networkReachabilityStatusBlock(status);
        }

    };

    // 生成網路監控上下文
    SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
    // 設定網路監控回撥
    SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
    // 放到執行迴圈中
    SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

    // 在全域性併發佇列中非同步監聽SCNetworkReachabilityRef物件的網路狀態,如果發生變化則進行回撥和傳送通知
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
        SCNetworkReachabilityFlags flags;
        if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
            AFPostReachabilityStatusChange(flags, callback);
        }
    });
}

- (void)stopMonitoring {
    // 如果沒有傳入或者生成SCNetworkReachabilityRef物件就直接返回
    if (!self.networkReachability) {
        return;
    }

    // 將SCNetworkReachabilityRef物件從執行迴圈中移除
    SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}

- (NSString *)localizedNetworkReachabilityStatusString {
    return AFStringFromNetworkReachabilityStatus(self.networkReachabilityStatus);
}

- (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block {
    self.networkReachabilityStatusBlock = block;
}

複製程式碼

這其中有一點是在使用GCD時傳入了引數DISPATCH_QUEUE_PRIORITY_BACKGROUND,這個不常用的引數有著和AFURLRequestSerialization類中的throttleBandwidthWithPacketSize:delay:方法共同的知識點,感興趣的話可以看這篇文章

2.6.KVO

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
    if ([key isEqualToString:@"reachable"] || [key isEqualToString:@"reachableViaWWAN"] || [key isEqualToString:@"reachableViaWiFi"]) {
        return [NSSet setWithObject:@"networkReachabilityStatus"];
    }

    return [super keyPathsForValuesAffectingValueForKey:key];
}
複製程式碼

新增依賴鍵,使reachablereachableViaWWANreachableViaWiFi屬性依賴於networkReachabilityStatus屬性,當networkReachabilityStatus屬性值發生變化時,reachablereachableViaWWANreachableViaWiFi屬性的觀察者也能得到通知。

原始碼閱讀系列:AFNetworking

原始碼閱讀:AFNetworking(一)——從使用入手

原始碼閱讀:AFNetworking(二)——AFURLRequestSerialization

原始碼閱讀:AFNetworking(三)——AFURLResponseSerialization

原始碼閱讀:AFNetworking(四)——AFSecurityPolicy

原始碼閱讀:AFNetworking(五)——AFNetworkReachabilityManager

原始碼閱讀:AFNetworking(六)——AFURLSessionManager

原始碼閱讀:AFNetworking(七)——AFHTTPSessionManager

原始碼閱讀:AFNetworking(八)——AFAutoPurgingImageCache

原始碼閱讀:AFNetworking(九)——AFImageDownloader

原始碼閱讀:AFNetworking(十)——AFNetworkActivityIndicatorManager

原始碼閱讀:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking

原始碼閱讀:AFNetworking(十二)——UIButton+AFNetworking

原始碼閱讀:AFNetworking(十三)——UIImageView+AFNetworking

原始碼閱讀:AFNetworking(十四)——UIProgressView+AFNetworking

原始碼閱讀:AFNetworking(十五)——UIRefreshControl+AFNetworking

原始碼閱讀:AFNetworking(十六)——UIWebView+AFNetworking

相關文章