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

来源:http://www.cnblogs.com/Mike-zh/archive/2016/02/01/5174238.html
-Advertisement-
Play Games

這一篇主要介紹使用AFN如何訪問HTTPS網站以及這些做法的實現原理,還有介紹AFN的網路狀態監測部分AFNetworkReachabilityManager,這個模塊會和蘋果官方發Reachability框架做一個對比。 本文所有的代碼都運行在iOS9.2的模擬器上,並且在info.plist對A


這一篇主要介紹使用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的地址欄中點擊被打了紅叉的鎖來查看這個頁面的證書頒發機構,
12306HTTPS證書
我們可以搜索到這個命名為'SRCA'的機構實際上是‘中鐵認證中心’也就是12306自己的認證系統,它是用了自己的認證系統給自己頒發了一個SSL加密證書,而Chrome怎麼會認可它呢。順便看了一下百度的證書:
baiduHTTPS證書
這是一個由美國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.

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

NSURLAuthenticationMethodHTTPBasic 
NSURLAuthenticationMethodHTTPDigest
NSURLAuthenticationMethodNTLM
NSURLAuthenticationMethodClientCertificate
NSURLAuthenticationMethodServerTrust

其中HTTP Basic、HTTP Digest與NTLM認證都是基於用戶名/密碼的認證,ClientCertificate(客戶端證書)認證要求從客戶端上傳證書。客戶端需要按照服務端指定的認證方法進行認證,否則可能會按照錯誤處理。例如使用HTTP Basic方式,客戶端需要將用戶名和密碼信息放到憑據中,然後傳遞給服務端;如果使用的是ServerTrust方式,那麼客戶端就要將信任的憑據發給服務端。
一般在HTTPS訪問的第3步過程中,服務端要求的認證方法幾乎總是ServerTrust方式。有遇到過一些網路代理工具使用HTTP Digest的驗證方式,在瀏覽器端進行訪問的時候就彈出一個要求輸入賬號和密碼的彈窗。

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

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

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

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSURL *url = [NSURL URLWithString:@"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 (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
    // 判斷伺服器的身份驗證的方法是否是:ServerTrust方式
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        
        // 創建一個新憑據,這個憑據指定了'握手'是被信任的
        NSURLCredential *credential = [NSURLCredential credentialForTrust: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中的證書導入到項目中,請參見下圖:
Chrome生成證書
將生成的證書文件kyfw.12306.cn.cer加入到xcode項目中,使用AFN按照如下方式調用即可:

NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"kyfw.12306.cn.cer" ofType:nil];
NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
NSSet *set = [[NSSet alloc] 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:nil success:^(NSURLSessionDataTask *task, id responseObject) {
    NSLog(@"%@", [[NSString alloc] 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 *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *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 = [NSURLCredential credentialForTrust: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.");
        return NO;
    }

    NSMutableArray *policies = [NSMutableArray array];
    if (self.validatesDomainName) {
        [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
        [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }

    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        return NO;
    }

    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone:
        default:
            return NO;
        case AFSSLPinningModeCertificate: {
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            for (NSData *certificateData in self.pinnedCertificates) {
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }
            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

            if (!AFServerTrustIsValid(serverTrust)) {
                return NO;
            }
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
                if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                    return YES;
                }
            }
            
            return NO;
        }
        case AFSSLPinningModePublicKey: {
            NSUInteger trustedPublicKeyCount = 0;
            NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);

            for (id trustChainPublicKey in publicKeys) {
                for (id pinnedPublicKey in self.pinnedPublicKeys) {
                    if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
                        trustedPublicKeyCount += 1;
                    }
                }
            }
            return trustedPublicKeyCount > 0;
        }
    }
    
    return NO;
}

這段長度為60行的代碼實現了這樣的過程:
第一個if分支是對自簽名訪問設立條件:
domain不存在,或者
不允許無效證書,或者
不需要驗證功能變數名稱,或者
SSLPinningMode不是AFSSLPinningModeNone,而且必須上傳了證書文件。如果是走了這個分支,就要求如果想要實現自簽名的HTTPS訪問成功,必須設置pinnedCertificates,且不能使用defaultPolicy,因為不能SSLPinningMode屬性是readonly的,而defaultPolicy在創建的時候已經設置SSLPinningMode屬性為AFSSLPinningModeNone。(我們剛纔的實現方案就是在這條分支下完成的)

接下來是這樣一塊代碼:

NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
    [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
    [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}

SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

if (self.SSLPinningMode == AFSSLPinningModeNone) {
    return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
    return NO;
}

它完成的工作是:
先用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; // 關鍵語句1
manager.securityPolicy.allowInvalidCertificates = YES; // 關鍵語句2
[manager GET:@"https://kyfw.12306.cn/otn/leftTicket/init" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
    NSLog(@"%@", responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];


// 方式二 需要將證書導入到項目中
// 準備:將證書的二進位讀取,放入set中
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"kyfw.12306.cn.cer" ofType:nil];
NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
NSSet *set = [[NSSet alloc] initWithObjects:cerData, nil];

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set]; // 關鍵語句1
manager.securityPolicy.allowInvalidCertificates = YES; // 關鍵語句2
[manager GET:@"https://kyfw.12306.cn/otn/leftTicket/init" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
    NSLog(@"%@", responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];

AFN的AFNetworkReachabilityManager和Reachability

有關AFNetworkReachabilityManager使用比較簡單,不做太多的解釋,只是羅列一些註意點。
AFN開啟必須開啟監控之後才能獲取到新的網路狀態,如果不開啟各種網路狀態都為不可到達,例如

AFNetworkReachabilityManager *reachabilityManager = [AFNetworkReachabilityManager sharedManager]; 

NSLog(@"%zd", reachabilityManager.isReachableViaWiFi); // 始終是0
NSLog(@"%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通知,可以接收這個通知完成回調。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Here is what we want to do 1. The data should be sorted when the table column header is clicked 2. The user should be able to sort in both the directi
  • To sort the data in Angular 1. Use orderBy filter {{ orderBy_expression | orderBy : expression : reverse}} Example : ng-repeat="employee in employees
  • 好吧,這章不像上章那麼水了,總是炒剩飯也不好。 關於AJAX 所謂Ajax,全名Asynchronous JavaScript and XML。(也就非同步的JS和XML) 簡單點來講就是不刷新頁面來發送和獲取數據,然後更新頁面。 Ajax的優勢 無需插件支持 優秀的用戶體驗 提高web程式的性能 減
  • 前言 安全性,總是一個不可忽視的問題。許多人都承認這點,但是卻很少有人真的認真地對待它。所以我們列出了這個清單,讓你在將你的應用部署到生產環境來給千萬用戶使用之前,做一個安全檢查。 以下列出的安全項,大多都具有普適性,適用於除了Node.js外的各種語言和框架。但是,其中也包含一些用Node.js寫
  • 寫了一個slideDoor,reset.css就不放上來了,自行添加吧! 1 <!DOCTYPE html> 2 <html> 3 4 <head> 5 <meta charset="UTF-8"> 6 <title>slideDoor</title> 7 <link type="text/css"
  • 一,效果圖。 二,工程圖。 三,代碼。 RootViewController.m #import "RootViewController.h" @interface RootViewController () @end @implementation RootViewController - (id
  • 首先申明下,本文為筆者學習《OpenGL ES應用開發實踐指南(Android捲)》的筆記,涉及的代碼均出自原書,如有需要,請到原書指定源碼地址下載。 《OpenGL ES學習筆記(二)——平滑著色、自適應寬高及三維圖像生成》中闡述的平滑著色、自適應寬高是為了實現在移動端模擬真實場景採用的方法,並且...
  • 【原】AFNetworking源碼閱讀(五) 本文轉載請註明出處 —— polobymulberry-博客園 1. 前言 上一篇中提及到了Multipart Request的構建方法- [AFHTTPRequestSerializer multipartFormRequestWithMethod:U
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...