AFNetworkReachabilityManager 是用來實(shí)時(shí)監(jiān)聽網(wǎng)絡(luò)狀態(tài)的。具體來說,是監(jiān)聽手機(jī)(主機(jī))是否能夠向外發(fā)出數(shù)據(jù)包。注意咆瘟,AFNetworkReachabilityManager 只是監(jiān)聽手機(jī)(主機(jī))能不能把數(shù)據(jù)包發(fā)出去,而不管目標(biāo)主機(jī)是否接受诽里。簡(jiǎn)而言之袒餐,就是確認(rèn)箭能不能射出去,但是不保證射的中谤狡。
AFNetworkReachabilityManager 在一般情況下的調(diào)用是 [[AFNetworkReachabilityManager sharedManager]startMonitoring]灸眼。由于對(duì)網(wǎng)絡(luò)狀態(tài)的監(jiān)聽,貫徹整個(gè)app的運(yùn)行豌汇,所以,一般情況下應(yīng)用開發(fā)中開發(fā)者不會(huì)手動(dòng)調(diào)用 stopMonitoring 方法泄隔。
這里需要注意的是拒贱,AFNetworkReachabilityManager 提供了多個(gè)獲取其實(shí)例的方法。sharedManager是獲取單例,而 manager 是獲取一個(gè)新初始化的實(shí)例逻澳,不是單例闸天。還有幾個(gè)別的獲取實(shí)例的方法如下:
- (instancetype)managerForDomain:(NSString *)domain; —— 根據(jù)域名來獲取一個(gè) ReachabilityManager 的實(shí)例;
-
(instancetype)managerForAddress:(const void *)address; —— 根據(jù) socket 地址(sockaddr_in6斜做,ipv6) 來獲取一個(gè) ReachabilityManager 的實(shí)例苞氮。
一般程序中,是用的都是 sharedManager瓤逼,而其他獲取實(shí)例的方法笼吟,可以讓開發(fā)者根據(jù)實(shí)際業(yè)務(wù)需求,對(duì)有需要的網(wǎng)絡(luò)進(jìn)行監(jiān)聽霸旗。
sharedManager中實(shí)際上是使用 dispatch_once 創(chuàng)建一個(gè)單例贷帮。
再來看下 manager 這個(gè)方法:
可以看到,這個(gè)方法里面诱告,先是創(chuàng)建了一個(gè) sockaddr 結(jié)構(gòu)體撵枢。這里,做了下版本控制精居,分別對(duì)應(yīng) ipv6 和 ipv4兩種 sockaddr 結(jié)構(gòu)體锄禽。然后調(diào)用 managerForAddress 方法:
managerForAddress中,調(diào)用 SCNetwork中的方法靴姿,創(chuàng)建一個(gè)用于監(jiān)聽網(wǎng)絡(luò)狀態(tài)的網(wǎng)絡(luò)狀態(tài)句柄(SCNetworkReachabilityRef)沃但。這里,SC 是 system configuration 的縮寫空猜,這是一個(gè) Apple 底層的庫绽慈。
最后看下 initWithReachability 這個(gè)初始化方法:
忙了這么一大圈,其實(shí)就是為了得到一個(gè) SCNetworkReachabilityRef辈毯,他在監(jiān)聽網(wǎng)絡(luò)狀態(tài)時(shí)很關(guān)鍵坝疼。SCNetworkReachabilityRef是一個(gè)結(jié)構(gòu)體,我們來看看 Apple 的代碼注釋:
文檔中說谆沃,SCNetworkReachabilityRef 的 api 使得一個(gè)應(yīng)用可以獲知系統(tǒng)當(dāng)前的網(wǎng)絡(luò)配置狀態(tài)钝凶,以及判斷網(wǎng)絡(luò)是否能夠連接目標(biāo)主機(jī)。此外唁影,當(dāng)網(wǎng)絡(luò)狀態(tài)發(fā)生改變時(shí)耕陷,可以通過通知監(jiān)聽網(wǎng)絡(luò)是否能夠連接【萆颍“Reachability”反映出哟沫,一個(gè)應(yīng)用向網(wǎng)絡(luò)堆棧發(fā)出的數(shù)據(jù)包,是否能夠從本機(jī)發(fā)出锌介。需要注意的是嗜诀,“Reachability”只是表征數(shù)據(jù)包能夠發(fā)出猾警,并不保證數(shù)據(jù)能夠切實(shí)的被主機(jī)收到。
可以看到隆敢,SCNetworkReachabilityRef 其實(shí)是一個(gè)網(wǎng)絡(luò)地址或者說名稱的句柄发皿,Apple把所有相關(guān)的一些列信息都用著一個(gè)句柄在操作。同時(shí)拂蝎,在Apple的代碼注釋中可以看到穴墅,要讓 SCNetworkReachabilityRef 正常工作的話,需要將 SCNetworkReachabilityRef 加入到 運(yùn)行循環(huán) 或者是 調(diào)度隊(duì)列中温自。
簡(jiǎn)單來說玄货,所謂的網(wǎng)絡(luò)監(jiān)聽,就是觀察 SCNetworkReachabilityRef 中的參數(shù)狀態(tài)捣作,所有的一切其實(shí)都是圍繞著他的誉结。在 SCNetworkReachabilityRef 中有 target host ,整個(gè)監(jiān)聽的過程券躁,其實(shí)就是監(jiān)聽能不能對(duì)這個(gè)目標(biāo)主機(jī)發(fā)出數(shù)據(jù)包惩坑。
簡(jiǎn)單的看下 startMonitoring 方法。startMonitoring 中也拜,主要是是對(duì) SCNetwork 這一Apple底層的網(wǎng)絡(luò)框架的使用以舒。SCNetwork 存在于 systemConfiguration庫中,顧名思義是對(duì)系統(tǒng)的配置慢哈。AFNetworking 中的 startMonitoring 方法實(shí)現(xiàn):
讓我們一步一步來看蔓钟。方法一開始會(huì)將上次的監(jiān)聽任務(wù)停止。隨后卵贱,判斷 self 的 networkReachability 是否為空滥沫。然后創(chuàng)建了一個(gè)閉包(block)這個(gè) block 是使用于 SCNetworkReachabilityRef 的回調(diào)中的。使用 __weak 目的是為了不讓 self 的引用計(jì)數(shù) +1键俱,使用 __strong 是為了讓實(shí)例在 block 運(yùn)行完之前不被釋放兰绣。當(dāng)然,如果你使用的是 shareManager 的話编振,那么其實(shí)這里的 __weak 和 __strong 都沒有存在的必要缀辩,因?yàn)?shareManager 是一個(gè)單例。
block顯然是在網(wǎng)絡(luò)狀態(tài)發(fā)生變化時(shí)被調(diào)用的踪央,很簡(jiǎn)單臀玄,不多說了。之后是創(chuàng)建了一個(gè) SCNetworkReachabilityContext 的結(jié)構(gòu)體畅蹂。來看下 SCNetworkReachabilityContext 的定義:
SCNetworkReachabilityContext 包含了用戶指定的健无,相對(duì)于 SCNetworkReachability 的數(shù)據(jù)和回調(diào)函數(shù)。
version —— 版本號(hào)液斜,是傳遞給 SCDynamicStore (系統(tǒng)配置動(dòng)態(tài)存儲(chǔ)庫)來創(chuàng)建函數(shù)用的累贤。這個(gè)結(jié)構(gòu)體的版本號(hào)是 0
info —— 一個(gè) C 指針募胃,指向一個(gè)用戶指定的數(shù)據(jù)塊。(也就是前一步中創(chuàng)建的那個(gè)block)
retain —— 用在回調(diào) info 的時(shí)候畦浓,為其引用加1
release —— 用來對(duì)之前引用 +1 的 info,引用減1
retain 和 release 這里主要注意的是函數(shù)定義要正確检疫,不然會(huì)有不可知的結(jié)果讶请。這里有個(gè)疑問就是這兩個(gè) retain 和 release 究竟是干什么使的?先往下看屎媳。
copyDescription —— 回調(diào)時(shí)夺溢,為 info 提供描述。這個(gè)直接傳 NULL 就行烛谊,基本沒什么用风响。
然后,是 SCNetworkReachabilitySetCallback :
就是為 SCNetworkReachability添加回調(diào)函數(shù)丹禀,target 就是之前設(shè)定的 網(wǎng)絡(luò)句柄状勤。簡(jiǎn)單點(diǎn)說,他就是一個(gè)目標(biāo)双泪,監(jiān)聽本機(jī)能不能像他發(fā)出數(shù)據(jù)包(僅僅是監(jiān)聽能不能發(fā)出持搜,不保證是不是接受)。這個(gè)方法中還要穿入一個(gè)回調(diào)函數(shù)焙矛,和一個(gè) SCNetworkReachabilityContext 葫盼。回調(diào)函數(shù)用來當(dāng)網(wǎng)絡(luò)狀態(tài)發(fā)生變化時(shí)村斟,讓系統(tǒng)進(jìn)行回調(diào)贫导。最后一個(gè) context 是和 回調(diào)函數(shù)相關(guān)聯(lián)的 SCNetworkReachabilityContext (網(wǎng)絡(luò)可連接上下文)。這兒說的有點(diǎn)繞蟆盹,但繼續(xù)往下看孩灯。
這里要看下 SCNetworkReachabilityCallBack :
回調(diào)函數(shù)需要包含三個(gè)參數(shù),一個(gè)是監(jiān)聽的目標(biāo)日缨,一個(gè)是flag表示新的網(wǎng)絡(luò)狀態(tài)钱反,還有一個(gè)是一個(gè)C指針,指向用戶設(shè)定的閉包(也就是 SCNetworkReachabilityContext 中的 info)
到了這里應(yīng)該明白匣距,其實(shí) SCNetworkReachabilityContext 保存的是回調(diào)函數(shù)以及相關(guān)的一些數(shù)據(jù)操作面哥,SCNetworkReachabilityCallBack是回調(diào)函數(shù),其中的 info 來自于 SCNetworkReachabilityContext毅待。
我覺得 SCNetworkReachabilityContext 讓開發(fā)者能在設(shè)定回調(diào)函數(shù)時(shí)尚卫,有更多的自由,在 retain 和 release 的時(shí)候可以做更多的事兒尸红。(可能有更多的作用吱涉,但是我暫時(shí)沒有看出來刹泄,請(qǐng)知道的朋友不吝賜教)
到了這里,應(yīng)該對(duì)整個(gè)回調(diào)的結(jié)構(gòu)有一些認(rèn)識(shí)了怎爵。簡(jiǎn)而言之特石,先創(chuàng)建一個(gè)要監(jiān)聽的目標(biāo),然后把回調(diào)函數(shù)用 SCNetworkReachabilityContext 結(jié)構(gòu)體包裹鳖链。然后姆蘸,為這個(gè)監(jiān)聽目標(biāo)設(shè)置回調(diào)方法。當(dāng)然芙委,看到這里逞敷,你可能會(huì)覺得為什么這么麻煩?直接把前面創(chuàng)建的 callback作為回調(diào)不就得了灌侣?
這里有幾點(diǎn)推捐,第一 SCNetworkReachabilitySetCallback 中,對(duì)回調(diào)函數(shù)的參數(shù)類型是有要求的侧啼,需要回調(diào)函數(shù)接收三個(gè)參數(shù) —— target牛柒,callout,info痊乾。而 callback 只接受一個(gè)改變后的網(wǎng)絡(luò)狀態(tài)作為參數(shù)焰络。二者實(shí)際上是不同的函數(shù),直接傳符喝,或者強(qiáng)轉(zhuǎn)的話會(huì)出錯(cuò)闪彼。事實(shí)上,AFN在這里set的回調(diào)函數(shù) —— AFNetworkReachabilityCallback 仍然不是不是最終的真正的回調(diào)所在协饲∥吠螅看一下 AFNetworkReachabilityCallback:
處理網(wǎng)絡(luò)狀態(tài)變化的入口有兩個(gè),一個(gè)是設(shè)置回調(diào) block 茉稠,另一個(gè)是 接受通知描馅。AFPostReachabilityStatusChange 就是調(diào)用一次用戶設(shè)置的 回調(diào) block ,然后發(fā)一個(gè)通知而线。
可以看到铭污,AFNetworkReachabilityCallback 實(shí)際上還要調(diào)用 AFPostReachabilityStatusChange 。而在 AFPostReachabilityStatusChange 中并沒有用到 target 這個(gè)參數(shù)膀篮。這么寫嘹狞,看上去是比較繁瑣的,但是有一個(gè)巨大的好處 —— 一個(gè)方法對(duì)應(yīng)一個(gè)功能誓竿。
對(duì)于 AFNetworkReachabilityCallback 和 AFPostReachabilityStatusChange 確實(shí)只要對(duì) AFPostReachabilityStatusChange 做一下修改磅网,在其參數(shù)列表中,添加一個(gè) target 然后這個(gè) target 參數(shù)不去使用筷屡,就直接可以將其作為回調(diào)函數(shù)傳給 SCNetworkReachabilitySetCallback 了涧偷。這樣做在功能上是沒有問題的簸喂,但是如此一來這么設(shè)計(jì) 函數(shù)的 api 是不優(yōu)雅的 —— 函數(shù)中多了一個(gè)不需要的參數(shù)。而且燎潮,AFPostReachabilityStatusChange 本身是可以直接調(diào)用的喻鳄,而不僅僅是作為 SCNetworkReachabilitySetCallback 的參數(shù)。所以确封,AFN 的作者在這里使用了一個(gè)獨(dú)立的 AFNetworkReachabilityCallback 作為一個(gè)二傳手诽表,最終調(diào)用 AFPostReachabilityStatusChange。這也是給我們?cè)谌粘>幊虝r(shí)的一個(gè)啟發(fā) —— 盡量做到一個(gè)函數(shù)就是一個(gè)功能隅肥;設(shè)計(jì)函數(shù) api 時(shí)不要出現(xiàn)多余的入?yún)ⅰ?br> 在設(shè)置完了回調(diào)函數(shù)后,將 networkReachability 加入到 runloop中袄简,達(dá)到實(shí)時(shí)?監(jiān)聽的目的 —— SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
這個(gè) SCNetworkReachabilityScheduleWithRunLoop 方法比較簡(jiǎn)單腥放,就是將 networkReachability 添加到 某個(gè)運(yùn)行循環(huán)上(這里是主循環(huán)),然后設(shè)定一個(gè)模式绿语,在這個(gè)模式下都會(huì)觸發(fā)監(jiān)聽秃症。
這里選的是 kCFRunLoopCommonModes 這樣當(dāng)用戶操作UI的時(shí)候,?也會(huì)繼續(xù)監(jiān)聽吕粹。
最后种柑,直接在最低優(yōu)先級(jí)的的隊(duì)列上,調(diào)用一次 AFPostReachabilityStatusChange 函數(shù)匹耕,發(fā)送目前的網(wǎng)絡(luò)可連接狀態(tài)聚请。