【原】AFNetworking源碼閱讀(六)

来源:http://www.cnblogs.com/polobymulberry/archive/2016/02/02/5174298.html
-Advertisement-
Play Games

【原】AFNetworking源碼閱讀(六) 本文轉載請註明出處 —— polobymulberry-博客園 1. 前言 這一篇的想講的,一個就是分析一下AFSecurityPolicy文件,看看AFNetworking的網路安全策略,尤其指HTTPS(大家可以先簡單瞭解下HTTPS)。再一個就是分


【原】AFNetworking源碼閱讀(六)

本文轉載請註明出處 —— polobymulberry-博客園

1. 前言


這一篇的想講的,一個就是分析一下AFSecurityPolicy文件,看看AFNetworking的網路安全策略,尤其指HTTPS(大家可以先簡單瞭解下HTTPS)。再一個就是分析下AFNetworkReachabilityManager文件,看看AFNetworking如何解決網路狀態的檢測。

2. AFSecurityPolicy - 網路安全策略


之前我們在AFURLSessionManager中實現了NSURLSessionDelegate的代理方法- (void)URLSession:didReceiveChallenge:completionHandler:,其中我們提到過,如果iOS客戶端需要向伺服器發送一個憑證(Credential)來確認認證挑戰(authentication challenge),那麼先得使用AFNetworking的安全策略類-AFSecurityPolicy來檢查伺服器端是否可以信任(在authenticationMethod屬性為NSURLAuthenticationMethodServerTrust情況下),而檢查的方式就是通過- [AFSecurityPolicy evaluateServerTrust:forDomain:]這個函數。

2.1 - [AFSecurityPolicy evaluateServerTrust:forDomain:]

// 根據severTrust和domain來檢查伺服器端發來的證書是否可信
// 其中SecTrustRef是一個CoreFoundation類型,用於對伺服器端傳來的X.509證書評估的
// 而我們都知道,數字證書的簽發機構CA,在接收到申請者的資料後進行核對並確定信息的真實有效,然後就會製作一份符合X.509標準的文件。證書中的證書內容包含的持有者信息和公鑰等都是由申請者提供的,而數字簽名則是CA機構對證書內容進行hash加密後得到的,而這個數字簽名就是我們驗證證書是否是有可信CA簽發的數據。
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(nullable NSString *)domain;

具體函數我們直接看函數源碼:

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
    /**
self.allowInvalidCertificates==YES表示如果此處允許使用自建證書(伺服器自己弄的CA證書,非官方),並且還想驗證domain是否有效(self.validatesDomainName == YES),也就是說你想驗證自建證書的domain是否有效。那麼你必須使用pinnedCertificates(就是在客戶端保存伺服器端頒發的證書拷貝)才可以。但是你的SSLPinningMode為AFSSLPinningModeNone,表示你不使用SSL pinning,只跟瀏覽器一樣在系統的信任機構列表裡驗證服務端返回的證書。所以當然你的客戶端上沒有你導入的pinnedCertificates,同樣表示你無法驗證該自建證書。所以都返回NO。最終結論就是要使用伺服器端自建證書,那麼就得將對應的證書拷貝到iOS客戶端,並使用AFSSLPinningMode或AFSSLPinningModePublicKey
    if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
        // https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
        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) {
        // 如果需要驗證domain,那麼就使用SecPolicyCreateSSL函數創建驗證策略,其中第一個參數為true表示驗證整個SSL證書鏈,第二個參數傳入domain,用於判斷整個證書鏈上葉子節點表示的那個domain是否和此處傳入domain一致
        [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
        // 如果不需要驗證domain,就使用預設的BasicX509驗證策略
        [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }
    // 為serverTrust設置驗證策略,即告訴客戶端如何驗證serverTrust
    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
    // 如果SSLPinningMode為 AFSSLPinningModeNone,表示你不使用SSL pinning,但是我允許自建證書,那麼返回YES,或者使用AFServerTrustIsValid函數看看serverTrust是否可信任,如果信任,也返回YES
    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        // 既不允許自建證書,而且使用AFServerTrustIsValid函數又返回NO,那麼該serverTrust就真的不能通過驗證了
        return NO;
    }

    switch (self.SSLPinningMode) {
        // 理論上,上面那個部分已經解決了self.SSLPinningMode)為AFSSLPinningModeNone)等情況,所以此處再遇到,就直接返回NO
        case AFSSLPinningModeNone:
        default:
            return NO;
       // 這個模式表示用證書綁定(SSL Pinning)方式驗證證書,需要客戶端保存有服務端的證書拷貝
        // 註意客戶端保存的證書存放在self.pinnedCertificates中
        case AFSSLPinningModeCertificate: {
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            for (NSData *certificateData in self.pinnedCertificates) {
               // 這裡使用SecCertificateCreateWithData函數對原先的pinnedCertificates做一些處理,保證返回的證書都是DER編碼的X.509證書
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }
            // 將pinnedCertificates設置成需要參與驗證的Anchor Certificate(錨點證書,通過SecTrustSetAnchorCertificates設置了參與校驗錨點證書之後,假如驗證的數字證書是這個錨點證書的子節點,即驗證的數字證書是由錨點證書對應CA或子CA簽發的,或是該證書本身,則信任該證書),具體就是調用SecTrustEvaluate來驗證。
            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

            if (!AFServerTrustIsValid(serverTrust)) {
                return NO;
            }

            // 伺服器端的證書鏈,註意此處返回的證書鏈順序是從葉節點到根節點
            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
            // 從伺服器端證書鏈的根節點往下遍歷,看看是否有與客戶端的綁定證書一致的,有的話,就說明伺服器端是可信的。因為遍歷順序正好相反,所以使用reverseObjectEnumerator
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]){
                if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                    return YES;
                }
          }
            
            return NO;
        }
        // AFSSLPinningModePublicKey模式同樣是用證書綁定(SSL Pinning)方式驗證,客戶端要有服務端的證書拷貝,只是驗證時只驗證證書里的公鑰,不驗證證書的有效期等信息。只要公鑰是正確的,就能保證通信不會被竊聽,因為中間人沒有私鑰,無法解開通過公鑰加密的數據。
        case AFSSLPinningModePublicKey: {
            NSUInteger trustedPublicKeyCount = 0;
            // 從serverTrust中取出伺服器端傳過來的所有可用的證書,並依次得到相應的公鑰
            NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
            // 依次遍歷這些公鑰,如果和客戶端綁定證書的公鑰一致,那麼就給trustedPublicKeyCount加一
            for (id trustChainPublicKey in publicKeys) {
                for (id pinnedPublicKey in self.pinnedPublicKeys) {
                    if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
                        trustedPublicKeyCount += 1;
                    }
                }
            }
          // trustedPublicKeyCount大於0說明伺服器端中的某個證書和客戶端綁定的證書公鑰一致,認為伺服器端是可信的
            return trustedPublicKeyCount > 0;
        }
    }
    
    return NO;
}

上述函數實現中調用了AFSecurityPolicy的私有方法(註意evaluateServerTrust:forDomain:方法是AFSecurityPolicy比較重要的公開方法),下麵我來逐個分析相應的函數實現。

static BOOL AFServerTrustIsValid(SecTrustRef serverTrust)

static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
    BOOL isValid = NO;
    SecTrustResultType result;
    // 對照下麵的Require_noErr_Quiet函數解釋,此處errorCode指的就是SecTrustEvaluate(serverTrust, &result)函數的返回值。如果serverTrust評估出錯,那麼就直接執行return isValid,預設isValid為NO。
    __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
    // 如果SecTrustEvaluate函數評估沒出錯,那麼就看result的結果
    // 只有當result為kSecTrustResultUnspecified(此標誌表示serverTrust評估成功,此證書也被暗中信任了,但是用戶並沒有顯示地決定信任該證書),或者當result為kSecTrustResultProceed(此標誌表示評估成功,和上面不同的是該評估得到了用戶認可),這兩者取其一就可以認為對serverTrust評估成功
    // 在下麵有對result類型(SecTrustResultType)的簡單講解
    isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);

_out:
    return isValid;
}

// Require_noErr_Quiet是一個巨集定義函數,表示如果errorCode不為0(0表示沒有錯誤),那麼就使用C語言中的go語句,跳到對應exceptionLabel地方開始執行代碼。
#ifndef __Require_noErr_Quiet
    #define __Require_noErr_Quiet(errorCode, exceptionLabel)                      \
      do                                                                          \
      {                                                                           \
          if ( __builtin_expect(0 != (errorCode), 0) )                            \
          {                                                                       \
              goto exceptionLabel;                                                \
          }                                                                       \
      } while ( 0 )
#endif

// kSecTrustResultUnspecified和kSecTrustResultProceed都是SecTrustResultType類型
/** 
  SecTrustResultType中枚舉值的含義包括兩個方面:一個是指評估是否成功,另一個是指該評估結果是不是由用戶決定的。對於是不是由用戶決定的這個問題,上面kSecTrustResultUnspecified和kSecTrustResultProceed就是一個很好的例子
 */

static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust)

// 獲取到serverTrust中證書鏈上的所有證書
static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
    // 使用SecTrustGetCertificateCount函數t獲取到serverTrust中需要評估的證書鏈中的證書數目,並保存到certificateCount中
    CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
    NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
    // 使用SecTrustGetCertificateAtIndex函數獲取到證書鏈中的每個證書,並添加到trustChain中,最後返回trustChain
    for (CFIndex i = 0; i < certificateCount; i++) {
        SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
        [trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
    }

    return [NSArray arrayWithArray:trustChain];
}

static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust)

// 取出serverTrust中證書鏈上每個證書的公鑰,並返回對應的該組公鑰
static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
    // 接下來的一小段代碼和上面AFCertificateTrustChainForServerTrust函數的作用基本一致,都是為了獲取到serverTrust中證書鏈上的所有證書,並依次遍歷,取出公鑰。
    SecPolicyRef policy = SecPolicyCreateBasicX509();
    CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
    NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
    for (CFIndex i = 0; i < certificateCount; i++) {
        SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);

        SecCertificateRef someCertificates[] = {certificate};
        CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);

        SecTrustRef trust;
        // 根據給定的certificates和policy來生成一個trust對象
        __Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);

        SecTrustResultType result;
        // 使用SecTrustEvaluate來評估上面構建的trust
        __Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);
        // 如果該trust符合X.509證書格式,那麼先使用SecTrustCopyPublicKey獲取到trust的公鑰,再將此公鑰添加到trustChain中
        [trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];

    _out:
        // 註意釋放資源
        if (trust) {
            CFRelease(trust);
        }

        if (certificates) {
            CFRelease(certificates);
        }

        continue;
    }
    CFRelease(policy);
    // 返回對應的一組公鑰
    return [NSArray arrayWithArray:trustChain];
}

2.2 + [AFSecurityPolicy policyWithPinningMode:withPinnedCertificates:]

AFSecurityPolicy中還有一些關於初始化的函數,比較重要的就數+ [AFSecurityPolicy policyWithPinningMode:withPinnedCertificates:]這個函數了。

// 初始化AFSecurityPolicy對象的SSLPinningMode和pinnedCertificates兩個屬性
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {
    AFSecurityPolicy *securityPolicy = [[self alloc] init];
    securityPolicy.SSLPinningMode = pinningMode;

    [securityPolicy setPinnedCertificates:pinnedCertificates];

    return securityPolicy;
}

// 此函數設置securityPolicy中的pinnedCertificates屬性
// 註意還將對應的self.pinnedPublicKeys屬性也設置了,該屬性表示的是對應證書的公鑰(與pinnedCertificates中的證書是一一對應的)
- (void)setPinnedCertificates:(NSSet *)pinnedCertificates {
    _pinnedCertificates = pinnedCertificates;

    if (self.pinnedCertificates) {
        NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]];
        for (NSData *certificate in self.pinnedCertificates) {
            id publicKey = AFPublicKeyForCertificate(certificate);
            if (!publicKey) {
                continue;
            }
            [mutablePinnedPublicKeys addObject:publicKey];
        }
        self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys];
    } else {
        self.pinnedPublicKeys = nil;
    }
}

static id AFPublicKeyForCertificate(NSData *certificate)

// 此函數沒什麼特別要提及的,和AFPublicKeyTrustChainForServerTrust實現的原理基本一致
// 區別僅僅在該函數是返回單個證書的公鑰(所以傳入的參數是一個證書),而AFPublicKeyTrustChainForServerTrust返回的是serverTrust的證書鏈中所有證書公鑰
static id AFPublicKeyForCertificate(NSData *certificate) {
    id allowedPublicKey = nil;
    SecCertificateRef allowedCertificate;
    SecCertificateRef allowedCertificates[1];
    CFArrayRef tempCertificates = nil;
    SecPolicyRef policy = nil;
    SecTrustRef allowedTrust = nil;
    SecTrustResultType result;
    // 因為此處傳入的certificate參數是NSData類型的,所以需要使用SecCertificateCreateWithData來將NSData對象轉化為SecCertificateRef對象
    allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
    __Require_Quiet(allowedCertificate != NULL, _out);

    allowedCertificates[0] = allowedCertificate;
    tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);

    policy = SecPolicyCreateBasicX509();
    __Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
    __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);

    allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);

_out:
    if (allowedTrust) {
        CFRelease(allowedTrust);
    }

    if (policy) {
        CFRelease(policy);
    }

    if (tempCertificates) {
        CFRelease(tempCertificates);
    }

    if (allowedCertificate) {
        CFRelease(allowedCertificate);
    }

    return allowedPublicKey;
}

3. AFNetworkReachabilityManager


AFNetworkingReachabilityManager是我使用AFNetworking的時候,第一個接觸到的類。當時主要是用這個類做一些網路狀態判斷。比如我當時做一個視頻類的app時,考慮到網路如果是2G/3G/4G,那麼最好是有一個switchButton來讓用戶選擇是否使用2G/3G/4G來觀看線上視頻,此時你就需要判斷當前網路狀態,如果用戶不允許2G/3G/4G網路觀看線上視頻,而此時手機網路又是2G/3G/4G網路,就得提示用戶當前網路不支持播放線上視頻。

該類的使用方法很簡單,我們看一個簡單例子:

// 設置networkReachabilityStatusBlock,根據不同網路狀態,用戶自定義處理方式
AFNetworkReachabilityManager *manager = [AFNetworkReachabilityManager sharedManager];
[manager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
    NSLog(@"network status '%@'", AFStringFromNetworkReachabilityStatus(status));
}];
// 啟動網路監聽
[manager startMonitoring];

其實networkReachabilityStatusBlock應該都好理解。關鍵是這個startMonitoring方法。大家是不是覺得很神奇,感覺就像app在系統後臺有一個迴圈,不停地檢測網路狀態。那麼具體是怎麼做到的呢,這裡先透露點:代碼的核心使用了iOS提供的一系列SCNetworkReachability文件中的方法。

- (void)startMonitoring {
    // 先停止之前的網路監聽
    [self stopMonitoring];
    // networkReachability表示的是需要檢測的網路地址的句柄
    if (!self.networkReachability) {
        return;
    }

    __weak __typeof(self)weakSelf = self;
    // 根據網路狀態status來設置網路狀態監聽的回調函數callback
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;

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

    };
    /** context是一個結構體
         typedef struct {
         // 創建一個SCNetworkReachabilityContext結構體時,需要調用SCDynamicStore的創建函數,而此創建函數會根據version來創建出不同的結構體,SCNetworkReachabilityContext對應的version是0
         CFIndex        version;  
        // 下麵兩個block(release和retain)的參數就是info,此處表示的是網路狀態處理的回調函數
          void *        __nullable info;
        // 該retain block用於對info進行retain,下麵那個AFNetworkReachabilityRetainCallback核心就是調用了Block_copy(用於retain一個block函數,即在堆空間新建或直接引用一個block拷貝)
          const void    * __nonnull (* __nullable retain)(const void *info);
        // 該release block用於對info進行release,下麵那個AFNetworkReachabilityReleaseCallback核心就是調用了Block_release(用於release一個block函數,即將block從堆空間移除或移除相應引用)
          void        (* __nullable release)(const void *info);
        // 提供info的description,此處調用為NULL
        CFStringRef    __nonnull (* __nullable copyDescription)(const void *info);
        } SCNetworkReachabilityContext;
     */
    SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
    /**
     // 給客戶端指定對應target(該參數和需要檢測網路狀況的地址有一定關聯,此處使用的是self.networkReachability),然後當這個target的網路狀態變化時,告之SCNetworkReachabilityCallBack對象callout處理(此處使用的是AFNetworkReachabilityCallback),另外callout中使用到的參數包括target和context提供的info。
     Boolean
     SCNetworkReachabilitySetCallback    (
    SCNetworkReachabilityRef                                        target,
    SCNetworkReachabilityCallBack    __nullable    callout,
    SCNetworkReachabilityContext    * __nullable    context
)                __OSX_AVAILABLE_STARTING(__MAC_10_3,__IPHONE_2_0);
     */
    SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
    /**
     此處表示在main RunLoop中以kCFRunLoopCommonModes形式處理self.networkingReachability
     */
    SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
    // 在後臺檢測self.networkingReachability的網路狀態,並使用SCNetworkReachabilityGetFlags函數返回產生的flag,註意此處flag表示的就是網路的狀態,後面會詳細介紹每種flag對應的狀態是什麼
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
        SCNetworkReachabilityFlags flags;
        if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
            // AFPostReachabilityStatusChange函數就是先將flags轉化為對應的AFNetworkReachabilityStatus變數,然後給我們的callback處理,後面會詳解此函數
            AFPostReachabilityStatusChange(flags, callback);
        }
    });
}

static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block)

static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
    // 使用AFNetworkReachabilityStatusForFlags函數將flags轉化為status,提供給下麵block使用
    AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
    dispatch_async(dispatch_get_main_queue(), ^{
        if (block) {
            block(status);
        }
        // 對於用戶,可以使用KVO來觀察status的變化,隨後用戶可以根據傳過來的userInfo[AFNetworkingReachabilityNotificationStatusItem]獲取到相應的status
        NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
        NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
        [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
    });
}

static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) {
    // 該網路地址可達
    BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0);
    // 該網路地址雖然可達,但是需要先建立一個connection
    BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); 
    // 該網路雖然也需要先建立一個connection,但是它是可以自動去connect的
    BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0));
    // 不需要用戶交互,就可以connect上(用戶交互一般指的是提供網路的賬戶和密碼)
    BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0);
    // 如果isReachable==YES,那麼就需要判斷是不是得先建立一個connection,如果需要,那就認為不可達,或者雖然需要先建立一個connection,但是不需要用戶交互,那麼認為也是可達的
    BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction));
    
    //  AFNetworkReachabilityStatus就四種狀態Unknown、NotReachable、ReachableViaWWAN、ReachableViaWiFi,這四種狀態字面意思很好理解,這裡就不贅述了
    AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown;
    if (isNetworkReachable == NO) {
        status = AFNetworkReachabilityStatusNotReachable;
    }
#if    TARGET_OS_IPHONE
    else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
        status = AFNetworkReachabilityStatusReachableViaWWAN;
    }
#endif
    else {
        status = AFNetworkReachabilityStatusReachableViaWiFi;
    }

    return status;
}

此時AFNetworking如何判斷網路狀態的思路基本也理清楚了:先使用SCNetworkReachability相關函數得到網路狀態,不過此時的網路狀態還需要放到AFNetworking中封裝一層,以提供適合用戶使用的API(如isReachable、isReachableViaWWAN、isReachableViaWiFi),對於不同的網路狀態,用戶只需要定義自己的block進行處理就行

最後,不知道大家對_networkReachability這個屬性值是否有疑惑:源碼中定義了一個sharedManager,那麼sharedManager中的_networkReachability是如何設置的呢?一圖以蔽之,我就不贅述了。

image

4. 總結


這一篇作為AFNetworking源碼閱讀系列的最後一篇文章到此結束了。雖然還有部分UIKit+AFNetworking的內容沒說,但是基本也沒什麼難度了,圖片部分可以參考我之前寫的SDWebImage源碼分析。而其他UIKit部分,參考AFNetworking源碼閱讀第一篇文章中對AFNetworkActivityIndicatorManager和UIRefreshControl+AFNetworking的解讀,因為基本思路都是一樣的。

5. 參考文章



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

-Advertisement-
Play Games
更多相關文章
  • 向下滾動一定距離出現返回頂部按鈕代碼實例:返回頂部按鈕在很多網站都有應用,也確實非常的人性化,但是有些網站可能會更進一步,那就是在預設狀態下,返回頂部按鈕在預設狀態下是隱藏的,只有向下拖動滾動條一定的距離,按鈕才會顯示,下麵就通過代碼實例介紹一下如何實現此功能。代碼如下: <!DOCTYPE htm
  • js實現浮點數保留兩位小數代碼:過浮點數小數點後面的數字太長的話,可能需要進行截取操作,下麵是一段這樣的實例代碼和大家分享一下。代碼如下: var num=3.1415926; console.log(num.toFixed(2)) 以上代碼比較簡單,這裡就多介紹了,具體可以參閱javascript
  • package.json中會有dependencies定義了項目依賴的外部組件,這些外部組件的依賴都是帶有版本符號以表示被依賴組件的版本範圍。 { "dependencies" : { "foo" : "1.0.0 - 2.9999.9999" , "bar" : ">=1.0.2 <2.1.2"
  • 在AngularJS中如何實現一個Model的緩存呢?可以通過在Provider中返回一個構造函數,併在構造函數中設計一個緩存欄位,在本篇末尾將引出這種做法。一般來說,Model要賦值給Scope的某個變數。有的直接把對象賦值給Scope變數;有的在Provider中返回一個對象再賦值給Scope變
  • 在寫一個js圖片輪播的時候,遇到了一個瀏覽器報錯的問題,想了很久,後面解決了問題, 瀏覽器報錯如下: 在chrome瀏覽器報錯如下: Uncaught TypeError: Cannot read property 'style' of undefined 在firefox瀏覽器報錯如下: Type
  • Xcode7自2015年9上架以來也有段時間了, 使用Xcode7以及Xcode7.1\Xcode7.2的小伙伴會發現像VVDocumenter-Xcode\KSImageNamed-Xcode\HOStringSense-for-Xcode等等一系列的插件不能正常使用了,下麵我們就來解決一下該版本
  • 第一步 :獲取ShareSDK 為了集成ShareSDK,您首先需要到ShareSDK官方網站註冊並且創建應用,獲得ShareSDK的Appkey,然後到SDK的下載頁面下載SDK的壓縮包,解壓以後可以得到如下圖的目錄結構: ShareSDK在“ShareSDK for Android”目錄下,此目
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...