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