AFNetworking之AFNetworkReachabilityManager深入學習

OneAlon發表於2018-01-31

此文章對AFNetworkReachabilityManager的原始碼做一些閱讀記錄和個人的理解.

AFNetworkReachabilityManager個人理解為是對SCNetworkReachabilityRef的相關封裝實現的. 在講解AFNetworkReachabilityManager之前, 需要先對SCNetworkReachabilityRef有一些瞭解, 附上SCNetworkReachabilityRef監測網路狀態.

AFNetworkReachabilityManager對網路的監測使用如下:

    AFNetworkReachabilityManager *networkReachManager = [AFNetworkReachabilityManager sharedManager];
    [networkReachManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
        NSLog(@"%zd", status);
    }];
    // 開始監測網路
    [networkReachManager startMonitoring];
複製程式碼

當網路狀態發生變化時就會執行block, 將網路狀態AFNetworkReachabilityStatus返回給我們.

AFNetworkReachabilityManager.h檔案

/**
 網路型別

 - AFNetworkReachabilityStatusUnknown: 未知網路
 - AFNetworkReachabilityStatusNotReachable: 網路不可達, 無網路
 - AFNetworkReachabilityStatusReachableViaWWAN: 手機網路
 - AFNetworkReachabilityStatusReachableViaWiFi: WiFi
 */
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
    AFNetworkReachabilityStatusUnknown          = -1,
    AFNetworkReachabilityStatusNotReachable     = 0,
    AFNetworkReachabilityStatusReachableViaWWAN = 1,
    AFNetworkReachabilityStatusReachableViaWiFi = 2,
};
複製程式碼

對外提供四種網路狀態的列舉值.

/**
 The current network reachability status.
 當前網路狀態
 */
@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;

/**
 Whether or not the network is currently reachable.
 網路是否可用
 */
@property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;

/**
 Whether or not the network is currently reachable via WWAN.
 當前連線是否是WWAN
 */
@property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;

/**
 Whether or not the network is currently reachable via WiFi.
 當前連線是否是WiFi
 */
@property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;
複製程式碼

在.h檔案中對外提供了4個只讀屬性, 並且給出了對應的getter方法.

+ (instancetype)sharedManager;

+ (instancetype)manager;

+ (instancetype)managerForDomain:(NSString *)domain;

+ (instancetype)managerForAddress:(const void *)address;

- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;

- (nullable instancetype)init NS_UNAVAILABLE;
複製程式碼

提供幾個初始化方法, 建立例項物件.

/**
 Starts monitoring for changes in network reachability status.
  開始監聽
 */
- (void)startMonitoring;

/**
 Stops monitoring for changes in network reachability status.
  結束監聽
 */
- (void)stopMonitoring;

/**
 Returns a localized string representation of the current network reachability status.
 返回一個網路狀態的字串
 */
- (NSString *)localizedNetworkReachabilityStatusString;

/**
 Sets a callback to be executed when the network availability of the `baseURL` host changes.

 @param block A block object to be executed when the network availability of the `baseURL` host changes.. This block has no return value and takes a single argument which represents the various reachability states from the device to the `baseURL`.
 網路狀態改變的回撥
 監聽網路狀態的改變有兩種方法:1.是實現這個block 2.是監聽通知
 */
- (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block;
複製程式碼
/**
 Posted when network reachability changes.
 This notification assigns no notification object. The `userInfo` dictionary contains an `NSNumber` object under the `AFNetworkingReachabilityNotificationStatusItem` key, representing the `AFNetworkReachabilityStatus` value for the current network reachability.

 @warning In order for network reachability to be monitored, include the `SystemConfiguration` framework in the active target's "Link Binary With Library" build phase, and add `#import <SystemConfiguration/SystemConfiguration.h>` to the header prefix of the project (`Prefix.pch`).
 網路狀態改變時 傳送的通知
 在userInfo下有以AFNetworkingReachabilityNotificationStatusItem為key的一個NSNumber型別的值, 這個值對應著AFNetworkReachabilityStatus列舉, 反應網路狀態
 FOUNDATION_EXPORT主要用於定義常量
 */
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;

/**
 Returns a localized string representation of an `AFNetworkReachabilityStatus` value.
這個是定義的一個C語言函式, 返回本地化的status字串
 */
FOUNDATION_EXPORT NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status);
複製程式碼

定義了兩個通知的名稱, 當網路狀態改變時發出的通知, 接收的通知中會有一個userInfo, 可根據keyAFNetworkingReachabilityNotificationStatusItem取出通知的內容. 我們可以借鑑AFN中通知的實現, 在開發中可以將通知的key定義到一個專門管理常量的檔案中, 按照不同的模組進行劃分.

AFNetworkReachabilityManager.m檔案

// 一個靜態字串, 網路狀態發生變化時發出的通知 對應.h
NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change";
// 網路狀態發生變化時發出通知, 攜帶的資料
NSString * const AFNetworkingReachabilityNotificationStatusItem = @"AFNetworkingReachabilityNotificationStatusItem";

// 將列舉型別轉換成字串(這是對在.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);
    }
}
複製程式碼

對在.h檔案中定義的常量的賦值和函式的實現.

// 定義block型別, 當網路狀態改變時呼叫的block
typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status);
複製程式碼
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;
}
複製程式碼

根據SCNetworkReachabilityFlags這個網路標記轉換成AFN中的網路狀態 static修飾全域性, 只能在當前檔案使用 在我們開發的過程中經常會使用私有方法, 將私有方法寫成c語言函式的形式的好處(個人理解)(static void functionName(){}): 1.在檔案的最前邊, 方便查詢 2.可以使用行內函數, 提高效率

static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
    AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
    dispatch_async(dispatch_get_main_queue(), ^{
        if (block) {
            block(status);
        }
        NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
        NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
        [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
    });
}
複製程式碼

監聽網路狀態的改變有兩種, 一種是實現setReachabilityStatusChangeBlock:中的block, 另一種是監聽通知AFNetworkingReachabilityDidChangeNotification, 此方法將監聽網路狀態改變的兩種方式封裝到一個函式中, 在主佇列中非同步執行. 這種方法也是值得學習的地方.

static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
    AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}
複製程式碼

這個函式直接呼叫上邊的函式.

static const void * AFNetworkReachabilityRetainCallback(const void *info) {
    return Block_copy(info);
}

static void AFNetworkReachabilityReleaseCallback(const void *info) {
    if (info) {
        Block_release(info);
    }
}
複製程式碼

void *個人理解為OC中的id型別, 可以指向任何型別. block其實也是物件, 我們可以對其進行retain操作, 在block做為屬性的時候我們通常用copy去修飾block, 將block拷貝到堆記憶體中. 這兩個block用於建立SCNetworkReachabilityContext結構體, 下邊會講到.

// SCNetworkReachabilityRef 網路連線引用
@property (readonly, nonatomic, assign) SCNetworkReachabilityRef networkReachability;
// 網路狀態, 列舉型別
@property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
// 網路狀態切換block
@property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock;
複製程式碼

.m檔案中定義的三個屬性.

- (void)startMonitoring {
    [self stopMonitoring];

    // 控制程式碼
    if (!self.networkReachability) {
        return;
    }

    // callback回撥
    __weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;

        strongSelf.networkReachabilityStatus = status;
        if (strongSelf.networkReachabilityStatusBlock) {
            strongSelf.networkReachabilityStatusBlock(status);
        }

    };

    /**
     typedef struct {
     CFIndex        version;
     void *        __nullable info;// void * 相當於oc中的id型別, 可以指向任何型別的引數
     const void    * __nonnull (* __nullable retain)(const void *info);// 接收一個函式, 目的是對info做retain操作
     void        (* __nullable release)(const void *info);// 接收一個函式, 目的是對info做release操作
     CFStringRef    __nonnull (* __nullable copyDescription)(const void *info);// 接收一個函式, 根據info獲取description字串
     } SCNetworkReachabilityContext;

     context是一個結構體
     */
    SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
    // 根據上下文設定回撥
    SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
    // 加入執行緒池中 mainRunLoop commonModes
    SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
        SCNetworkReachabilityFlags flags;
        if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
            AFPostReachabilityStatusChange(flags, callback);
        }
    });
}
複製程式碼

這個是主要講解的方法, AFNetworkReachabilityManager監測網路狀態的核心就在此方法. SCNetworkReachabilityContext是一個結構體(個人理解為結構體主要用於儲存資料), void * __nullable info是指向需要執行的block的指標, 包含了使用者指定的資料和用於SCNetworkReachabilitySetCallback方法的回撥函式. SCNetworkReachabilitySetCallback設定回撥, 可以看我的另一篇文章有簡要介紹. SCNetworkReachabilityScheduleWithRunLoopnetworkReachability網路連線引用加入到執行迴圈中. 個人理解為加入執行迴圈以後會一直監測networkReachability網路狀態, 如果網路狀態有變化就會呼叫AFNetworkReachabilityCallback. 在非同步執行緒中傳送一次網路狀態, 呼叫SCNetworkReachabilityGetFlags獲取網路狀態, AFPostReachabilityStatusChange傳送網路狀態.

- (void)stopMonitoring {
    if (!self.networkReachability) {
        return;
    }

    SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}
複製程式碼

從指定的執行迴圈和模式中移除網路連線引用的排程. 也就是說不再監聽networkReachability的網路狀態.

#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];
}
複製程式碼

鍵值依賴, 返回一個鍵集合, 這些屬性的值會影響指定的key的值, 當集合中鍵的值發生變化時, 就會觸發指定key的監聽通知. 當networkReachabilityStatus的值發生變化時, 就會觸發指定的key的鍵值監聽方法.


利用一天的時間整理了一下, 如果有錯誤的地方, 希望能夠指出, 共同進步.

相關文章