AFNetworking 3.0 源碼閱讀筆記(六)

原文:http://itangqi.me/2016/05/17/the-notes-of-learning-afnetworking-six/

前言

AFNetworkReachabilityManager 是對 SystemConfiguration 模塊的封裝,蘋果的文檔中也有一個類似的項目 Reachability 這裏對網絡狀態的監控跟蘋果官方的實現幾乎是完全相同的。

同樣在 GitHub 上有一個類似的項目叫做 Reachability 不過這個項目由於命名的原因可能會在審覈時被拒絕

無論是 AFNetworkReachabilityManager,蘋果官方的項目或者說 GitHub 上的 Reachability,它們的實現都是類似的,而在這裏我們會以 AFNetworking 中的 AFNetworkReachabilityManager 爲例來說明在 iOS 開發中,我們是怎樣監控網絡狀態的。


AFNetworkReachabilityManager 的使用和實現

AFNetworkReachabilityManager 的使用還是非常簡單的,只需要三個步驟,就基本可以完成對網絡狀態的監控:

  1. 初始化 AFNetworkReachabilityManager
  2. 調用 startMonitoring 方法開始對網絡狀態進行監控
  3. 設置 networkReachabilityStatusBlock 在每次網絡狀態改變時, 調用這個 block

初始化 AFNetworkReachabilityManager

在初始化方法中,使用 SCNetworkReachabilityCreateWithAddress 或者 SCNetworkReachabilityCreateWithName 生成一個 SCNetworkReachabilityRef 的引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+ (instancetype)managerForDomain:(NSString *)domain {
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);

    AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];

    return manager;
}

+ (instancetype)managerForAddress:(const void *)address {
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
    AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];

    return manager;
}
  1. 這兩個方法會通過一個域名或者一個 sockaddr_in 的指針生成一個 SCNetworkReachabilityRef
  2. 調用 - [AFNetworkReachabilityManager initWithReachability:] 將生成的 SCNetworkReachabilityRef 引用傳給 networkReachability
  3. 設置一個默認的 networkReachabilityStatus
1
2
3
4
5
6
7
8
9
10
11
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
    self = [super init];
    if (!self) {
        return nil;
    }

    self.networkReachability = CFBridgingRelease(reachability);
    self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;

    return self;
}

當調用 CFBridgingRelease(reachability) 後,會把 reachability 橋接成一個 NSObject 對象賦值給 self.networkReachability,然後釋放原來的 CoreFoundation 對象。

監控網絡狀態

在初始化 AFNetworkReachabilityManager 後,會調用 startMonitoring 方法開始監控網絡狀態:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- (void)startMonitoring {
    // 1. 先調用 `- stopMonitoring` 方法,如果之前設置過對網絡狀態的監聽,使用 `SCNetworkReachabilityUnscheduleFromRunLoop` 方法取消之前在 Main Runloop 中的監聽
    [self stopMonitoring];

    if (!self.networkReachability) {
        return;
    }
    
    __weak __typeof(self)weakSelf = self;
    // 2. 創建一個在每次網絡狀態改變時的回調
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;

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

    };

    id networkReachability = self.networkReachability;
    // 3. 創建一個 `SCNetworkReachabilityContext`,其中的 `callback` 就是上一步中的創建的 block 對象
    SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
    // 4. 當目標的網絡狀態改變時,會調用傳入的回調
    SCNetworkReachabilitySetCallback((__bridge SCNetworkReachabilityRef)networkReachability, AFNetworkReachabilityCallback, &context);
    // 5. 在 Main Runloop 中對應的模式開始監控網絡狀態
    SCNetworkReachabilityScheduleWithRunLoop((__bridge SCNetworkReachabilityRef)networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
    // 6. 獲取當前的網絡狀態,調用 callback
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
        SCNetworkReachabilityFlags flags;
        if (SCNetworkReachabilityGetFlags((__bridge SCNetworkReachabilityRef)networkReachability, &flags)) {
            AFPostReachabilityStatusChange(flags, callback);
        }
    });
}

1. 關於 AFNetworkReachabilityStatus

1
2
3
4
5
6
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
    AFNetworkReachabilityStatusUnknown          = -1,
    AFNetworkReachabilityStatusNotReachable     = 0,
    AFNetworkReachabilityStatusReachableViaWWAN = 1,
    AFNetworkReachabilityStatusReachableViaWiFi = 2,
};

2. 關於 SCNetworkReachabilityContext

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct {
    // 創建一個 SCNetworkReachabilityContext 結構體時,需要調用 SCDynamicStore 的創建函數,而此創建函數會根據 version 來創建出不同的結構體,SCNetworkReachabilityContext 對應的 version 是 0
    CFIndex		version;
    // 下面兩個block(release 和 retain)的參數就是 info,此處表示的是網絡狀態處理的回調函數
    void *		__nullable info;
    // 該 retain block 用於對 info 進行 retain,下面那個 AFNetworkReachabilityRetainCallback 核心就是調用了 Block_copy(用於 retain 一個 block 函數,即在堆空間新建或直接引用一個 block 拷貝)
    const void	* __nonnull (* __nullable retain)(const void *info);
    // 該 release block 用於對 info 進行 release,下面那個 AFNetworkReachabilityReleaseCallback 核心就是調用了 Block_release(用於 release 一個 block 函數,即將 block 從堆空間移除或移除相應引用)
    void		(* __nullable release)(const void *info);
    // 提供 info 的 description,此處調用爲 NULL
    CFStringRef	__nonnull (* __nullable copyDescription)(const void *info);
} SCNetworkReachabilityContext;

在下一節中會介紹上面所提到的一些 C 函數以及各種回調。

設置 networkReachabilityStatusBlock 以及回調

在 Main Runloop 中對網絡狀態進行監控之後,在每次網絡狀態改變,就會調用 AFNetworkReachabilityCallback 函數:

1
2
3
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
    AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}

這裏會從 info 中取出之前存在 context 中的 AFNetworkReachabilityStatusBlock

1
2
3
4
5
6
7
8
9
10
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
    __strong __typeof(weakSelf)strongSelf = weakSelf;

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

};

取出這個 block 之後,傳入 AFPostReachabilityStatusChange 函數:

1
2
3
4
5
6
7
8
9
10
11
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];
    });
}
  1. 調用 AFNetworkReachabilityStatusForFlags 獲取當前的網絡可達性狀態
  2. 在主線程中異步執行上面傳入的 callback block(設置 self 的網絡狀態,調用 networkReachabilityStatusBlock
  3. 發送 AFNetworkingReachabilityDidChangeNotification 通知.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) {
    // 該網絡地址可達
    BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0);
    // 該網絡地址雖然可達,但是需要先建立一個 connection
    BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0);
    // 該網絡雖然也需要先建立一個 connection,但是它是可以自動去 connect 的
    BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0));
    // 不需要用戶交互,就可以 connect 上(用戶交互一般指的是提供網絡的賬戶和密碼)
    BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0);
    // 如果 isReachable==YES,那麼就需要判斷是不是得先建立一個 connection,如果需要,那就認爲不可達,或者雖然需要先建立一個 connection,但是不需要用戶交互,那麼認爲也是可達的
    BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction));
    
    //  AFNetworkReachabilityStatus 就四種狀態 Unknown、NotReachable、ReachableViaWWAN、ReachableViaWiFi,這四種狀態字面意思很好理解,這裏就不贅述了
    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;
}

因爲 flags 是一個 SCNetworkReachabilityFlags,它的不同位代表了不同的網絡可達性狀態,通過 flags 的位操作,獲取當前的狀態信息 AFNetworkReachabilityStatus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef CF_OPTIONS(uint32_t, SCNetworkReachabilityFlags) {
    kSCNetworkReachabilityFlagsTransientConnection = 1<<0,
    kSCNetworkReachabilityFlagsReachable = 1<<1,
    kSCNetworkReachabilityFlagsConnectionRequired = 1<<2,
    kSCNetworkReachabilityFlagsConnectionOnTraffic = 1<<3,
    kSCNetworkReachabilityFlagsInterventionRequired = 1<<4,
    kSCNetworkReachabilityFlagsConnectionOnDemand = 1<<5, // __OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_3_0)
    kSCNetworkReachabilityFlagsIsLocalAddress = 1<<16,
    kSCNetworkReachabilityFlagsIsDirect = 1<<17,
#if  TARGET_OS_IPHONE
    kSCNetworkReachabilityFlagsIsWWAN = 1<<18,
#endif  // TARGET_OS_IPHONE

    kSCNetworkReachabilityFlagsConnectionAutomatic = kSCNetworkReachabilityFlagsConnectionOnTraffic
};

這裏就是在 SystemConfiguration 中定義的全部的網絡狀態的標誌位。


與 AFNetworking 協作

其實 AFNetworkReachabilityManagerAFNetworking 整個框架並沒有太多的耦合。正相反,它在整個框架中作爲一個即插即用的類使用,每一個 AFURLSessionManager 都會持有一個 AFNetworkReachabilityManager 的實例。

1
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];

這是整個框架中除了 AFNetworkReachabilityManager.h/m 文件,唯一一個引用到這個類的地方。


總結

  • AFNetworkReachabilityManager 實際上只是對底層 SystemConfiguration 庫中的 C 函數封裝的類(類似於 GCD),它爲我們隱藏了 C 語言的實現,提供了統一且簡潔的 Objective-C 語言接口
  • 它是 AFNetworking 中一個即插即用的模塊

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章