通讀AFN③--HTTPS訪問控制(AFSecurityPolicy),Reachability(AFNetworkReachabilityManager)

原文來自:http://www.cnblogs.com/Mike-zh/p/5174238.html

這一篇主要介紹使用AFN如何訪問HTTPS網站以及這些做法的實現原理,還有介紹AFN的網絡狀態監測部分AFNetworkReachabilityManager,這個模塊會和蘋果官方推薦的Reachability框架做一個對比。

本文所有的代碼都運行在iOS9.2的模擬器上,并且在info.plist對ATS做了適配:設置允許非法的加載Allow Arbitrary Loads為YES。

不要認為在info.plist添加NSAppTransportSecurity>NSAllowsArbitraryLoads為YES

就以為弄懂iOS9網絡適配了,有關具體細節問題請看南峰子的這篇文章App Transport Security(ATS)

介于iOS有關HTTPS訪問的認證過程代碼并不是特別經常使用,本文會用大量的篇幅介紹HTTPS認證的過程,并會通過系統的NSURLSession完成一些認證相關的代碼,畢竟AFN就是使用了這些代碼來實現對HTTPS網站的訪問支持的。

HTTPS網站訪問過程中,瀏覽器幫你做了什么

不同于普通的HTTP請求,當訪問一個HTTPS的網站時,瀏覽器會幫我們很多隱藏的工作,這其實是SSL通道建立的三次握手過程:

1.發起請求。

首先當輸入完https網址敲擊回車之后,瀏覽器首先向服務器發送一個需要訪問的請求,這個請求中包含著瀏覽器SSL 協議的版本號,加密算法的種類,產生的隨機數,以及其他服務器和客戶端之間通訊所需要的各種信息。

2.服務端返回證書。

服務器向客戶端傳送SSL 協議的版本號,加密算法的種類,隨機數以及其他相關信息,同時服務器還將向客戶端傳送自己的證書,這些信息被保存在客戶端被稱作'被保護空間'的地方。這里最關鍵的就是證書信息。

3.瀏覽器驗證證書信息。

瀏覽器利用服務器傳過來的信息驗證服務器的合法性,服務器的合法性包括:證書是否過期,發行服務器證書的CA 是否可靠,發行者證書的公鑰能否正確解開服務器證書的“發行者的數字簽名”,服務器證書上的域名是否和服務器的實際域名相匹配。

如果合法性驗證沒有通過,通訊將斷開;如果合法性驗證通過,將繼續進行第四步。

4.客戶端向服務器發送“預主密碼”。

瀏覽器隨機產生一個用于后面通訊的“對稱密碼”,然后用服務器的公鑰(服務器的公鑰從步驟②中的服務器的證書中獲得)對其加密,然后將加密后的“預主密碼”傳給服務器。

4.1.如果服務器要求客戶的身份認證(在握手過程中為可選),用戶不光要傳給服務器“預主密碼”,還需建立一個隨機數然后對其進行數據簽名,將這個含有簽名的隨機數和客戶自己的證書也傳給服務器。

4.2.如果不需要,則只將“預主密碼”傳給服務器,并直接進行第6步。

5.服務端身份驗證(需要才進行)。

如果服務器要求客戶的身份認證,服務器必須檢驗客戶證書和簽名隨機數的合法性,具體的合法性驗證過程包括:客戶的證書使用日期是否有效,為客戶提供證書的CA 是否可靠,發行CA 的公鑰能否正確解開客戶證書的發行CA 的數字簽名,檢查客戶的證書是否在證書廢止列表(CRL)中。

檢驗如果沒有通過,通訊立刻中斷;

如果驗證通過,進行下一步。

6.瀏覽器、服務端各自生成通話密碼。

服務器將用自己的私鑰解開加密的“預主密碼”,然后執行一系列步驟來產生主通訊密碼(客戶端也將通過同樣的方法產生相同的主通訊密碼)。

7.約定通話密碼。

服務器和客戶端用相同的主通訊密碼即“通話密碼”,一個對稱密鑰用于SSL 協議的安全數據通訊的加解密通訊。同時在SSL 通訊過程中還要完成數據通訊的完整性,防止數據通訊中的任何變化。

8.瀏覽器通知服務器已準備就緒。

客戶端向服務器端發出信息,指明后面的數據通訊將使用的步驟⑦中的主密碼為對稱密鑰,同時通知服務器客戶端的握手過程結束。

9.服務端通知瀏覽器已準備就緒。

服務器向客戶端發出信息,指明后面的數據通訊將使用的步驟⑦中的主密碼為對稱密鑰,同時通知客戶端服務器端的握手過程結束。

10.開始數據通訊。

SSL 的握手部分結束,SSL安全通道建立完成,開始進行數據通訊開始,通訊過程中客戶和服務器開始使用相同的對稱密鑰。

如果以https://www.baidu.com為例,這時候已經表現為baidu的主頁打開了,但是SSL加密通道在下次請求的時候不用再次建立。

對于訪問的過程中,通常會在第3步出現問題,以12306的購票頁面為例:

當進行到第3步的時候,瀏覽器驗證為:發行服務器證書的CA是不可靠的,可以在Chrome的地址欄中點擊被打了紅叉的鎖來查看這個頁面的證書頒發機構,

我們可以搜索到這個命名為'SRCA'的機構實際上是‘中鐵認證中心’也就是12306自己的認證系統,它是用了自己的認證系統給自己頒發了一個SSL加密證書,而Chrome怎么會認可它呢。順便看了一下百度的證書:

這是一個由美國Symantec Trust Network組織頒發的證書,是一個比較權威的證書頒發機構,幾乎在所有的瀏覽器中都是認可的。而baidu使用的證書是這個機構的根證書的子證書,而之所以瀏覽器能認可它,是因為根證書通過webtrust國際認證,并已經內置到各大瀏覽器如谷歌,火狐,微軟等系統中。

那么這畢竟只是瀏覽器默認的一種認證方式,畢竟我們還是需要訪問12306的,這里就要改變一下第3步驗證的結果,在瀏覽器中,我們可以手動選擇信任,然后繼續向下進行。

這樣就能訪問這些網站了。

使用系統的NSURLSession模擬瀏覽器完成HTTPS的證書認證

與瀏覽器的驗證過程相似,iOS的HTTPS驗證過程也要走類似的步驟,不過不用擔心的是,很多過程我們也不需要處理,只需要處理好第3步就行了,當我們進行訪問一個HTTPS網站時,當走到第二步的時候,也就是服務器返回證書時,需要我們在本地自己完成證書信任的過程,如果使用session創建的task進行網絡訪問,這時候就會進入到- URLSession:didReceiveChallenge:completionHandler:這個代理方法中,這時候已經完成了HTTPS訪問的第二步,session會讓我們在這個方法中完成第3步的過程。這個方法的參數有如下的解釋:

參數解釋

challenge一個包含了授權請求的對象

completionHandler你的代理方法一定會調用的一個handler. 它的參數是

disposition—描述challenge如何被處理的幾個常量中的一個

credential—如果disposition是NSURLSessionAuthChallengeUseCredential,credential是授權驗證時會被使用到的憑據,其他情況為NULL.

challenge參數需要另外說明的是challenge是一個NSURLAuthenticationChallenge對象,代表著進行https請求進行時,服務端發送過來的質詢,當接收到質詢之后就要開始進行客戶端的驗證了。

這個對象中最重要的屬性就是protectionSpace它代表著對需要驗證的受保護空間的驗證,是一個NSURLProtectionSpace類型的對象。NSURLProtectionSpace對象包含請求的主機host、端口號port、代理類型proxyType、使用的協議protocol、服務端要求客戶端對其驗證的方法authenticationMethod等重要的信息,還有代表著服務器SSL傳輸狀態的SecTrustRef類型的屬性serverTrust,不過當且僅當authenticationMethod為NSURLAuthenticationMethodServerTrust這個屬性值才不為Nil.

這里還要說明一下服務端指定的驗證方法的類型,驗證方法的類型有很多種,這里不再一一列舉,我們通常會見到這樣幾種類型:

NSURLAuthenticationMethodHTTPBasicNSURLAuthenticationMethodHTTPDigestNSURLAuthenticationMethodNTLMNSURLAuthenticationMethodClientCertificateNSURLAuthenticationMethodServerTrust

其中HTTP Basic、HTTP Digest與NTLM認證都是基于用戶名/密碼的認證,ClientCertificate(客戶端證書)認證要求從客戶端上傳證書。客戶端需要按照服務端指定的認證方法進行認證,否則可能會按照錯誤處理。例如使用HTTP Basic方式,客戶端需要將用戶名和密碼信息放到憑據中,然后傳遞給服務端;如果使用的是ServerTrust方式,那么客戶端就要將信任的憑據發給服務端。

一般在HTTPS訪問的第3步過程中,服務端要求的認證方法幾乎總是ServerTrust方式。有遇到過一些網絡代理工具使用HTTP Digest的驗證方式,在瀏覽器端進行訪問的時候就彈出一個要求輸入賬號和密碼的彈窗。

對于completionHandler參數是一個最終處理憑據的回調,要求在創建好包含驗證信息的憑據之后必須調用,這樣才會將驗證的信息發送給服務端,也就意味著第3步的完成,開始進行第4步。

它的第一個參數是處理的選項,是一個枚舉類型:

typedefNS_ENUM(NSInteger,NSURLSessionAuthChallengeDisposition) {NSURLSessionAuthChallengeUseCredential=0,// 使用服務器發回的憑據,不過可能為空NSURLSessionAuthChallengePerformDefaultHandling=1,// 默認的處理方法,憑據參數會被忽略NSURLSessionAuthChallengeCancelAuthenticationChallenge=2,//取消整個請求,忽略憑據參數NSURLSessionAuthChallengeRejectProtectionSpace=3,// 這次質詢被拒絕,下次再試 ,憑據參數被忽略}NS_ENUM_AVAILABLE(NSURLSESSION_AVAILABLE,7_0);

理清上面的思路之后,我們可以試一試使用系統的session訪問HTTPS網站了:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {NSURL*url = [NSURLURLWithString:@"https://www.baidu.com"];? ? [[self.session dataTaskWithURL:url completionHandler:^(NSData* _Nullable data,NSURLResponse* _Nullable response,NSError* _Nullable error) {if(error) {NSLog(@"%@", error);return;? ? ? ? }NSLog(@"%@", response);? ? }] resume];}#pragma mark - NSURLSessionDelegate- (void)URLSession:(NSURLSession*)session didReceiveChallenge:(NSURLAuthenticationChallenge*)challenge completionHandler:(void(^)(NSURLSessionAuthChallengeDispositiondisposition,NSURLCredential* __nullable credential))completionHandler {// 判斷服務器的身份驗證的方法是否是:ServerTrust方式if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {// 創建一個新憑據,這個憑據指定了'握手'是被信任的NSURLCredential*credential = [NSURLCredentialcredentialForTrust:challenge.protectionSpace.serverTrust];if(credential !=nil) {// 完成'處置',將信任憑據發給服務端completionHandler(NSURLSessionAuthChallengeUseCredential, credential);? ? ? ? }// 如果credential == nil 以下回調會自動完成// completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, credential);}}

因為我們使用的是使用第2步中服務端傳回來的證書,所以即使是對付https://kyfw.12306.cn/otn/leftTicket/init這樣的流氓頁面也同樣是可以的。但是對于iOS9來說并不是這樣,必須設置了Allow Arbitrary Loads為YES才會達到預期效果。

對于AFN,無論實在iOS9之前還是iOS9之后,當訪問https://kyfw.12306.cn/otn/leftTicket/這個頁面的時候都會走不通,這是因為AFN對于自簽名的HTTPS網站有著特殊的驗證(有關驗證細節,請看本文下一部分),必須證書提前導入到項目中,將Chrome中的證書導入到項目中,請參見下圖:

將生成的證書文件kyfw.12306.cn.cer加入到xcode項目中,使用AFN按照如下方式調用即可:

NSString*cerPath = [[NSBundlemainBundle] pathForResource:@"kyfw.12306.cn.cer"ofType:nil];NSData*cerData = [NSDatadataWithContentsOfFile:cerPath];NSSet*set = [[NSSetalloc] initWithObjects:cerData,nil];AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];manager.responseSerializer = [AFHTTPResponseSerializer serializer];manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set];manager.securityPolicy.allowInvalidCertificates =YES;[manager GET:@"https://kyfw.12306.cn/otn/leftTicket/init"parameters:nilsuccess:^(NSURLSessionDataTask*task,idresponseObject) {NSLog(@"%@", [[NSStringalloc] initWithData:responseObject encoding:NSUTF8StringEncoding]);} failure:^(NSURLSessionDataTask*task,NSError*error) {NSLog(@"%@",error);}];

這樣便能正確的訪問自簽名的網站了。

AFN實現HTTPS訪問的細節

說了那么多如何使用代碼訪問HTTPS網站,那么AFN是如何實現的呢,AFURLSessionManager中實現了- URLSession:didReceiveChallenge:completionHandler:代理方法:

- (void)URLSession:(NSURLSession*)session? ? ? ? ? ? ? task:(NSURLSessionTask*)taskdidReceiveChallenge:(NSURLAuthenticationChallenge*)challenge completionHandler:(void(^)(NSURLSessionAuthChallengeDispositiondisposition,NSURLCredential*credential))completionHandler{NSURLSessionAuthChallengeDispositiondisposition =NSURLSessionAuthChallengePerformDefaultHandling;? ? __blockNSURLCredential*credential =nil;if(self.taskDidReceiveAuthenticationChallenge) {? ? ? ? disposition =self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);? ? }else{if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {if([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {? ? ? ? ? ? ? ? disposition =NSURLSessionAuthChallengeUseCredential;? ? ? ? ? ? ? ? credential = [NSURLCredentialcredentialForTrust:challenge.protectionSpace.serverTrust];? ? ? ? ? ? }else{? ? ? ? ? ? ? ? disposition =NSURLSessionAuthChallengeRejectProtectionSpace;? ? ? ? ? ? }? ? ? ? }else{? ? ? ? ? ? disposition =NSURLSessionAuthChallengePerformDefaultHandling;? ? ? ? }? ? }if(completionHandler) {? ? ? ? completionHandler(disposition, credential);? ? }}

它的思路上這樣的

如果主動通過manger的setTaskDidReceiveAuthenticationChallengeBlock:方法傳遞了taskDidReceiveAuthenticationChallenge的值那么,會按照傳入的block處理這次質詢,

如果沒有傳入就走AFN處理方式(else分支):

如果驗證方法為ServerTrust就會使用securityPolicy屬性的方法針對host評判serverTrust的合法性,如果成功了就會使用服務端傳來的證書進行處理,失敗了則會拒絕本次質詢。

如果驗證方法不是ServerTrust,則使用默認的處理方式(NSURLSessionAuthChallengePerformDefaultHandling)處理。

那么,可以看出,這里最關鍵的就是評判合法性的過程了,我們重點來看一下。評判合法性的方法被定義在AFSecurity類中,是這個類唯一的對象方法:

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust? ? ? ? ? ? ? ? ? forDomain:(NSString*)domain{if(domain &&self.allowInvalidCertificates &&self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] ==0)) {NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");returnNO;? ? }NSMutableArray*policies = [NSMutableArrayarray];if(self.validatesDomainName) {? ? ? ? [policies addObject:(__bridge_transferid)SecPolicyCreateSSL(true, (__bridgeCFStringRef)domain)];? ? }else{? ? ? ? [policies addObject:(__bridge_transferid)SecPolicyCreateBasicX509()];? ? }? ? SecTrustSetPolicies(serverTrust, (__bridgeCFArrayRef)policies);if(self.SSLPinningMode == AFSSLPinningModeNone) {returnself.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);? ? }elseif(!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {returnNO;? ? }switch(self.SSLPinningMode) {caseAFSSLPinningModeNone:default:returnNO;caseAFSSLPinningModeCertificate: {NSMutableArray*pinnedCertificates = [NSMutableArrayarray];for(NSData*certificateDatainself.pinnedCertificates) {? ? ? ? ? ? ? ? [pinnedCertificates addObject:(__bridge_transferid)SecCertificateCreateWithData(NULL, (__bridgeCFDataRef)certificateData)];? ? ? ? ? ? }? ? ? ? ? ? SecTrustSetAnchorCertificates(serverTrust, (__bridgeCFArrayRef)pinnedCertificates);if(!AFServerTrustIsValid(serverTrust)) {returnNO;? ? ? ? ? ? }for(NSData*trustChainCertificatein[serverCertificates reverseObjectEnumerator]) {if([self.pinnedCertificates containsObject:trustChainCertificate]) {returnYES;? ? ? ? ? ? ? ? }? ? ? ? ? ? }returnNO;? ? ? ? }caseAFSSLPinningModePublicKey: {NSUIntegertrustedPublicKeyCount =0;NSArray*publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);for(idtrustChainPublicKeyinpublicKeys) {for(idpinnedPublicKeyinself.pinnedPublicKeys) {if(AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {? ? ? ? ? ? ? ? ? ? ? ? trustedPublicKeyCount +=1;? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? }? ? ? ? ? ? }returntrustedPublicKeyCount >0;? ? ? ? }? ? }returnNO;}

這段長度為60行的代碼實現了這???的過程:

第一個if分支是對自簽名訪問設立條件:

domain不存在,或者

不允許無效證書,或者

不需要驗證域名,或者

SSLPinningMode不是AFSSLPinningModeNone,而且必須上傳了證書文件。如果是走了這個分支,就要求如果想要實現自簽名的HTTPS訪問成功,必須設置pinnedCertificates,且不能使用defaultPolicy,因為不能SSLPinningMode屬性是readonly的,而defaultPolicy在創建的時候已經設置SSLPinningMode屬性為AFSSLPinningModeNone。(我們剛才的實現方案就是在這條分支下完成的)

接下來是這樣一塊代碼:

NSMutableArray*policies = [NSMutableArrayarray];if(self.validatesDomainName) {? ? [policies addObject:(__bridge_transferid)SecPolicyCreateSSL(true, (__bridgeCFStringRef)domain)];}else{? ? [policies addObject:(__bridge_transferid)SecPolicyCreateBasicX509()];}SecTrustSetPolicies(serverTrust, (__bridgeCFArrayRef)policies);if(self.SSLPinningMode == AFSSLPinningModeNone) {returnself.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);}elseif(!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {returnNO;}

它完成的工作是:

先用policies數組組裝驗證策略,在通過SecTrustSetPolicies函數給serverTrust設置驗證策略,不過AFN并沒有接收函數的返回值,查看是否設置成功,不知道是為什么。

當SSLPinningMode為AFSSLPinningModeNone時,如果允許無效的證書(allowInvalidCertificates = YES)直接返回評測成功,如果不允許,按照剛才的驗證策略驗證,返回的是驗證的結果。

當SSLPinningMode不是AFSSLPinningModeNone時,如果既沒有驗證成功又不允許無效證書,則直接返回評測失敗。

(這里讓我想到了另一種訪問12306實現的方案:

manager.securityPolicy.validatesDomainName =NO;manager.securityPolicy.allowInvalidCertificates =YES;

既不用使用證書,也不用自己創建securityPolicy。

)

接下來看一下那個長長的switch:

如果self.SSLPinningMode是AFSSLPinningModeCertificate:取出self.pinnedCertificates中的所有證書,通過SecTrustSetAnchorCertificates函數設置證書驗證策略,失敗則直接返回評測失敗,否則檢查本地的證書是否包含服務端的證書

,如果是返回評測成功,否則返回評測失敗。

如果self.SSLPinningMode是AFSSLPinningModePublicKey:取出服務端證書的所有公鑰,和self.pinnedPublicKeys中所有公鑰,遍歷檢查有沒有相等的兩項,有則返回評測成功。我嘗試給securityPolicy的pinnedPublicKeys賦值一個公鑰集合,但是它并沒有對外提供接口,self.pinnedPublicKeys是一個私有屬性,并且是計算型的,是從本地的證書self.pinnedCertificates中提取出來的。

有關AFSecurityPolicy最核心的部分基本上將完了,最后我們還是要總結一下,訪問可惡的12306的兩種方法:

// 方式一 兩句就可以AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];manager.responseSerializer = [AFHTTPResponseSerializer serializer];manager.securityPolicy.validatesDomainName =NO;// 關鍵語句1manager.securityPolicy.allowInvalidCertificates =YES;// 關鍵語句2[manager GET:@"https://kyfw.12306.cn/otn/leftTicket/init"parameters:nilsuccess:^(NSURLSessionDataTask*task,idresponseObject) {NSLog(@"%@", responseObject);} failure:^(NSURLSessionDataTask*task,NSError*error) {}];// 方式二 需要將證書導入到項目中// 準備:將證書的二進制讀取,放入set中NSString*cerPath = [[NSBundlemainBundle] pathForResource:@"kyfw.12306.cn.cer"ofType:nil];NSData*cerData = [NSDatadataWithContentsOfFile:cerPath];NSSet*set = [[NSSetalloc] initWithObjects:cerData,nil];AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];manager.responseSerializer = [AFHTTPResponseSerializer serializer];manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set];// 關鍵語句1manager.securityPolicy.allowInvalidCertificates =YES;// 關鍵語句2[manager GET:@"https://kyfw.12306.cn/otn/leftTicket/init"parameters:nilsuccess:^(NSURLSessionDataTask*task,idresponseObject) {NSLog(@"%@", responseObject);} failure:^(NSURLSessionDataTask*task,NSError*error) {}];

AFN的AFNetworkReachabilityManager和Reachability

有關AFNetworkReachabilityManager使用比較簡單,不做太多的解釋,只是羅列一些注意點。

AFN開啟必須開啟監控之后才能獲取到新的網絡狀態,如果不開啟各種網絡狀態都為不可到達,例如

AFNetworkReachabilityManager *reachabilityManager = [AFNetworkReachabilityManager sharedManager];NSLog(@"%zd", reachabilityManager.isReachableViaWiFi);// 始終是0NSLog(@"%zd", reachabilityManager.isReachable);NSLog(@"%zd", reachabilityManager.isReachableViaWWAN);

即使開啟了網絡監控,也無法再第一時間獲取到網絡狀態,例如下面的代碼執行之后,第一時間查看各種狀態依然不可達,這是因為它會在網絡狀況改變時,異步改變單例中存儲的狀態。

AFNetworkReachabilityManager *reachabilityManager = [AFNetworkReachabilityManager sharedManager];[reachabilityManager startMonitoring];// 從開啟監控? 到得到下列值需要一定的時間NSLog(@"%zd", reachabilityManager.isReachableViaWiFi);// 立刻調用為0 ,過一段時間后準確NSLog(@"%zd", reachabilityManager.isReachable);// 立刻調用為0 ,過一段時間后準確NSLog(@"%zd", reachabilityManager.isReachableViaWWAN);// 立刻調用為0 ,過一段時間后準確

其實我使用較多的還是Reachability框架,

Reachability具有獲取實時網絡狀態的-currentReachabilityStatus方法,不需要開啟監控,只要用實例調用即可。

Reachability同樣可以進行網絡狀態改變的監控,可以用-startNotifier方法開啟,但是沒法傳入回調。但是每當網絡狀態改變的時候會發送一個kReachabilityChangedNotification通知,可以接收這個通知完成回調。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容