AFNetworking源碼學(xué)習(xí)(一)- AFNetworkReachabilityManager

AFNetworkReachabilityManager是監(jiān)控網(wǎng)絡(luò)環(huán)境變化的類(lèi)

AFNetworkReachabilityManager.h

See Apple's Reachability Sample Code ( https://developer.apple.com/library/ios/samplecode/reachability/ )

從這部分注釋可知,AFNetworkReachabilityManager這個(gè)類(lèi)參考自蘋(píng)果官方Reachability樣例代碼。

#import <SystemConfiguration/SystemConfiguration.h>

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

從這部分可以看出,網(wǎng)絡(luò)監(jiān)控的實(shí)現(xiàn)依賴(lài)SystemConfiguration這個(gè)api;至于NS_ENUM定義了AFNetworkReachabilityStatus網(wǎng)絡(luò)狀態(tài)的枚舉。

NS_ASSUME_NONNULL_BEGIN
NS_ASSUME_NONNULL_END

這個(gè)是為了swift的可選類(lèi)型配置添加的,其中間的參數(shù)默認(rèn)都是nonnull的。

接下來(lái),聲明了以下幾個(gè)屬性:

  • networkReachabilityStatus:網(wǎng)絡(luò)狀態(tài)
  • reachable:是否是可達(dá)的
  • reachableViaWWAN:是否是WWAN
  • reachableViaWiFi:是否是WiFi

注意BOOL屬性一般是要寫(xiě)getter方法的:

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

然后,就是初始化方法:

+ (instancetype)sharedManager;
+ (instancetype)manager;
+ (instancetype)managerForDomain:(NSString *)domain;
+ (instancetype)managerForAddress:(const void *)address;
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER; 

5個(gè)初始化方法,滿(mǎn)足了大部分需求,同樣的,參考蘋(píng)果官方的類(lèi),以NSString為例:

+ string
- init
- initWithBytes:length:encoding:
- initWithString:
- initWithFormat:
.....

所以,封裝一個(gè)類(lèi)的時(shí)候,應(yīng)多考慮不同的場(chǎng)景、不同的需求。

// 開(kāi)始監(jiān)控
- (void)startMonitoring;
// 停止監(jiān)控   
- (void)stopMonitoring; 
// 獲取網(wǎng)絡(luò)狀態(tài)的本地語(yǔ)言的字符串   
- (NSString *)localizedNetworkReachabilityStatusString;
// 設(shè)置網(wǎng)絡(luò)狀態(tài)改變的block回調(diào)
- (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block;

監(jiān)聽(tīng)網(wǎng)絡(luò)改變的回調(diào)有兩種方式:

  • 使用setReachabilityStatusChangeBlock:方法
  • 監(jiān)聽(tīng)AFNetworkingReachabilityDidChangeNotification通知

在.h文件最后使用了FOUNDATION_EXPORT聲明了兩個(gè)通知key的常量和一個(gè)函數(shù):

FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;
// 根據(jù)網(wǎng)絡(luò)狀態(tài)獲取相應(yīng)字符串
FOUNDATION_EXPORT NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status);

蘋(píng)果官方樣例代碼使用的是extern,那這里為什么使用FOUNDATION_EXPORT呢?
查看FOUNDATION_EXPORT的定義:

#if defined(__cplusplus)
#define FOUNDATION_EXTERN extern "C"
#else
#define FOUNDATION_EXTERN extern
#endif

#if TARGET_OS_WIN32

    #if defined(NSBUILDINGFOUNDATION)
        #define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllexport)
    #else
        #define FOUNDATION_EXPORT FOUNDATION_EXTERN __declspec(dllimport)
    #endif

    #define FOUNDATION_IMPORT FOUNDATION_EXTERN __declspec(dllimport)

#else
    #define FOUNDATION_EXPORT  FOUNDATION_EXTERN
    #define FOUNDATION_IMPORT FOUNDATION_EXTERN
#endif

#if !defined(NS_INLINE)
    #if defined(__GNUC__)
        #define NS_INLINE static __inline__ __attribute__((always_inline))
    #elif defined(__MWERKS__) || defined(__cplusplus)
        #define NS_INLINE static inline
    #elif defined(_MSC_VER)
        #define NS_INLINE static __inline
    #elif TARGET_OS_WIN32
        #define NS_INLINE static __inline__
    #endif
#endif

可以知道,F(xiàn)OUNDATION_EXPORT相當(dāng)于C語(yǔ)言中的extern、C++中的extern "C",還有Win32上的類(lèi)似特性,所以可以說(shuō)FOUNDATION_EXPORT是通用的,而且它能夠隱藏定義細(xì)節(jié),這應(yīng)該就是使用FOUNDATION_EXPORT的原因吧。

AFNetworkReachabilityManager.m

首先就是兩個(gè)通知key值常量的具體定義:

NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change";
NSString * const AFNetworkingReachabilityNotificationStatusItem = @"AFNetworkingReachabilityNotificationStatusItem";

但是,我覺(jué)得這塊處理的不好,且風(fēng)格不統(tǒng)一,我們可參考蘋(píng)果官方的:

NSString * const kReachabilityChangedNotification = @"kNetworkReachabilityChangedNotification";

使用k做前綴代表key值常量,可讀性好,且易于查找。

typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status);

定義了一個(gè)回傳status的block。

接下來(lái)就是.h文件聲明的AFStringFromNetworkReachabilityStatus函數(shù)的實(shí)現(xiàn),主要是根據(jù)status獲取本地化的字符串,值得一提的是NSLocalizedStringFromTable,它與NSLocalizedString的區(qū)別在于,存放本地化字符串的文件是自定義的,這里就是AFNetworking.strings,而不是Localizable.strings,第三方框架使用自定義文件名是必要的,易于區(qū)分,且可避免沖突。

再之后就是幾個(gè)私有方法,后面三個(gè)是輔助方法就不說(shuō)了,重要的是前面兩個(gè):

  • 第一個(gè)方法,是根據(jù)SCNetworkReachabilityFlags這個(gè)標(biāo)記轉(zhuǎn)換成自定義的AFNetworkReachabilityStatus枚舉類(lèi)型;
  • 第二個(gè)方法,是當(dāng)AFNetworkReachabilityStatus改變時(shí)block回調(diào),并發(fā)送AFNetworkingReachabilityDidChangeNotification通知。

值得注意的是,私有方法的寫(xiě)法:static void function(),平時(shí)我們可能是這樣寫(xiě):- (void)function;查看了蘋(píng)果官方樣例代碼,也是它這樣寫(xiě)的,以及一些著名的開(kāi)發(fā)框架都是這樣寫(xiě)的,那為什么要這樣寫(xiě)呢?

使用static用于函數(shù)定義時(shí),對(duì)函數(shù)的連接方式產(chǎn)生影響,使得函數(shù)只在本文件內(nèi)部有效,對(duì)其他文件是不可見(jiàn)的。這樣的函數(shù)又叫作靜態(tài)函數(shù),這種方式對(duì)函數(shù)本身是一種保護(hù)機(jī)制。而用在這里,在文件最前方,易于查找;且可適當(dāng)使用內(nèi)聯(lián)函數(shù),提高效率。

而初始化方法中,前面四個(gè)用的都是類(lèi)方法,第一個(gè)是創(chuàng)建單例,調(diào)用了+manager,該方法使用預(yù)編譯條件編譯,用于兼容iOS9之后推出的IPv6,而最后調(diào)用了+managerForAddress:這個(gè)方法,該方法是根據(jù)地址初始化,而+managerForDomain:是根據(jù)域名初始化。具體實(shí)現(xiàn)就不多說(shuō)了,都是根據(jù)官方樣例代碼而來(lái)的。而最后-initWithReachability:根據(jù)SCNetworkReachabilityRef初始化,并賦了初值:

_networkReachability = CFRetain(reachability);   //此處用了CFRetain,釋放的時(shí)候注意要CFRelease
self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;//初值為未知狀態(tài)

繼續(xù)往下,就是dealloc方法:

- (void)dealloc {
    [self stopMonitoring];//停止監(jiān)控
    
    if (_networkReachability != NULL) {
        CFRelease(_networkReachability);//CF類(lèi)型的,且CFRetain賦值過(guò),一定要CFRelease處理
    }
}

然后就是,isReachable、isReachableViaWWAN、isReachableViaWiFi三個(gè)getter方法,我們可以看到,都是鍵值關(guān)聯(lián)的,且都圍繞著self.networkReachabilityStatus這個(gè)值。在文件最后看到KVO的實(shí)現(xiàn):

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

    return [super keyPathsForValuesAffectingValueForKey:key];
}

更加證實(shí)這一點(diǎn),它們是鍵值依賴(lài)的,self.networkReachabilityStatus這個(gè)值變化,其他三個(gè)BOOL值也相應(yīng)的變化。注冊(cè)鍵值依賴(lài)這樣的做法,值得我們學(xué)習(xí),這可以讓代碼看起來(lái)更加精簡(jiǎn)、優(yōu)雅。

至于-localizedNetworkReachabilityStatusString、-setReachabilityStatusChangeBlock:這兩個(gè)方法就不講了,getter、setter方法,很簡(jiǎn)單。

接下來(lái),就是這個(gè)類(lèi)的兩個(gè)核心方法:

  • startMonitoring:開(kāi)始監(jiān)控
  • stopMonitoring:停止監(jiān)控

startMonitoring方法中,其核心是:

SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, 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)) {
            AFPostReachabilityStatusChange(flags, callback);
        }
    });

SCNetworkReachabilityContext是一個(gè)結(jié)構(gòu)體,其定義是:

typedef struct {
    CFIndex     version;
    void *      __nullable info;
    const void  * __nonnull (* __nullable retain)(const void *info);
    void        (* __nullable release)(const void *info);
    CFStringRef __nonnull (* __nullable copyDescription)(const void *info);
} SCNetworkReachabilityContext;

第一個(gè)參數(shù),看CFIndex的定義:

typedef signed long CFIndex;

可知,這是一個(gè)signed long類(lèi)型的參數(shù);
第二個(gè)參數(shù),是一個(gè)void * 類(lèi)型的值,即可以指向任何類(lèi)型的參數(shù);
第三個(gè)參數(shù),是一個(gè)函數(shù),目的是對(duì)info做retain操作;
第四個(gè)參數(shù),是一個(gè)函數(shù),目的是對(duì)info做release操作;
第五個(gè)參數(shù),是一個(gè)函數(shù),根據(jù)info獲取Description字符串。

那么,攜帶的info即為callback,也就是下面這個(gè)block:

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

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

    };

查看SCNetworkReachabilityContext的文檔:

Structure containing user-specified data and callbacks used with SCNetworkReachabilitySetCallback.

使用SCNetworkReachabilitySetCallback設(shè)置其回調(diào):

SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);

文檔說(shuō)明:which receives callbacks when the reachability of the target changes.(當(dāng)目標(biāo)的可達(dá)性變化時(shí)接收回調(diào))。

SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

SCNetworkReachabilityScheduleWithRunLoop的文檔中說(shuō)明:Schedules the specified network target with the specified run loop and mode.
安排指定的網(wǎng)絡(luò)目標(biāo)到指定的run loop和mode中。

接下來(lái),開(kāi)一個(gè)異步線程,用于發(fā)送網(wǎng)絡(luò)狀態(tài):

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

stopMonitoring中剛好相反:

SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

不安排到run loop和mode中,即停止監(jiān)控。

總結(jié)

雖然這是一個(gè)并不復(fù)雜的類(lèi),且很多實(shí)現(xiàn)都來(lái)源于官方樣例代碼,但其中很多細(xì)節(jié)和技巧是值得我們學(xué)習(xí)的:

  • NS_ENUM枚舉的恰當(dāng)使用
  • NS_ASSUME_NONNULL_BEGIN、NS_ASSUME_NONNULL_END
  • BOOL屬性一般要寫(xiě)getter方法,使用鍵值依賴(lài)能事半功倍
  • 考慮不同場(chǎng)景、不同需求,提供相應(yīng)的初始化方法
  • FOUNDATION_EXPORT聲明常量以及函數(shù)是通用的,且需注意key值常量的格式統(tǒng)一
  • 注意字符串的本地化
  • 回調(diào)使用block、notification,便于使用者選擇
  • 靜態(tài)函數(shù)用于私有方法,放在文件最前面,易于查找管理
  • CF類(lèi)型,注意CFRelease
  • 適當(dāng)場(chǎng)景,使用異步線程

參考資料:
AFNetworking 3.0 源碼解讀(一)之 AFNetworkReachabilityManager
AFNetworkReachabilityManager Class Reference
Reachability

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,648評(píng)論 19 139
  • 一、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡(jiǎn)單分配策略的問(wèn)題地址空間不隔離內(nèi)存使用效率低程序運(yùn)行的地址不確定 關(guān)于...
    SeanCST閱讀 8,137評(píng)論 0 27
  • 此系列文章都是轉(zhuǎn)載文章 文章出處 做ios開(kāi)發(fā),AFNetworking 這個(gè)網(wǎng)絡(luò)框架肯定都非常熟悉,也許我們平...
    泥孩兒0107閱讀 338評(píng)論 0 0
  • *面試心聲:其實(shí)這些題本人都沒(méi)怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來(lái)就是把...
    Dove_iOS閱讀 27,628評(píng)論 30 472
  • 前言 人生苦多,快來(lái) Kotlin ,快速學(xué)習(xí)Kotlin! 什么是Kotlin? Kotlin 是種靜態(tài)類(lèi)型編程...
    任半生囂狂閱讀 26,703評(píng)論 9 118

友情鏈接更多精彩內(nèi)容