AFNetworking底層源碼解析

一.簡介

AFNetworking是適用於iOS,macOS,watchOS和tvOS的的網絡庫。它構建於Foundation URL系統之上,擴展了Cocoa內置的強大的高級網絡抽象。它採用模塊化架構,設計精良,功能豐富的API,使用起來很是簡單。本文重點介紹緩存和安全兩個模塊;objective-c

二. 組織架構圖

三. 總體流程、緩存模塊和安全模塊介紹

3.1 總體流程圖

3.1.1 發送一個Get請求的流程圖

3.2 緩存

3.2.1 簡介

AFNetWorking是基於NSURLSession(iOS7以上的網絡請求框架),在生成配置的時候有三種緩存配置選擇:數據庫

// 默認會話模式:默認添加內存緩存和磁盤緩存。
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;  

// 瞬時會話模式:只添加內存緩存,不實現磁盤緩存。
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;  

// 後臺會話模式:內存和磁盤都不進行緩存。
+ (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier;  
複製代碼

咱們還能夠對緩存的大小進行設置,只須要對NSURLCache進行初始化就能夠了。數組

3.2.2 實現初始化

-application:didFinishLaunchingWithOptions:中對[NSURLCache sharedURLCache]進行初始化設置:緩存

NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
                                                         diskCapacity:20 * 1024 * 1024
                                                             diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
複製代碼

也能夠單獨對NSURLSession的configuration進行設置, 在AFNetWorking中對於圖片網絡請求設置了20M的內存緩存和150M的硬盤緩存:安全

+ (NSURLCache *)defaultURLCache {
return [[NSURLCache alloc] initWithMemoryCapacity:20 * 1024 * 1024
                                     diskCapacity:150 * 1024 * 1024
	                                       diskPath:@"com.alamofire.imagedownloader"];
}
複製代碼

3.2.3 緩存策略

簡介:緩存策略是指對網絡請求緩存處理是使用緩存仍是不使用:服務器

NSURLRequestUseProtocolCachePolicy:對特定的URL請求使用網絡協議中實現的緩存邏輯,這是默認的策略。
NSURLRequestReloadIgnoringLocalCacheData:數據須要從原始地址加載,不使用現有緩存。NSURLRequestReloadIgnoringLocalAndRemoteCacheData:不只忽略本地緩存,同時也忽略代理服務器或其餘中間介質目前已有的、協議容許的緩存。
NSURLRequestReturnCacheDataElseLoad:不管緩存是否過時,先使用本地緩存數據。若是緩存中沒有請求所對應的數據,那麼從原始地址加載數據。
NSURLRequestReturnCacheDataDontLoad:不管緩存是否過時,先使用本地緩存數據。若是緩存中沒有請求所對應的數據,那麼放棄從原始地址加載數據,請求視爲失敗(即:「離線」模式)。
NSURLRequestReloadRevalidatingCacheData:從原始地址確認緩存數據的合法性後,緩存數據就可使用,不然從原始地址加載。
複製代碼

3.2.4 圖片硬盤緩存

簡介:就是咱們常說的把數據保存在本地,好比FMDB、CoreData、歸檔、NSUserDefaults、NSFileManager等等,這裏就很少說了。 AFNetWorking3.0沒有直接作圖片硬盤緩存,而是經過URL緩存作的硬盤緩存。也就是說,若是內存緩存沒有讀取到圖片,就會調用下載邏輯,經過下載緩存的內存緩存硬盤緩存來獲取到已下載過的圖片,若是沒有下載過,就會從新下載。 若是咱們本身作圖片硬盤緩存建議使用NSFileManager,由於通常圖片data會比較大,測試證實路徑緩存會比放在數據庫有更高的性能。網絡

3.2.5 內存緩存映射的類:AFAutoPurgingImageCache

1) 簡介

AFAutoPurgingImageCache是協議定義了一組API,用於同步地從緩存中添加、刪除和獲取圖像。session

2)主要API介紹以下

3)內存緩存流程圖

4)內存緩存流程介紹

​ 每次下載完圖片將圖片添加到緩存中以前,先去檢測一下當前已緩存memoryUsage的大小,若是超過總緩存memoryCapacity,則獲取須要清理的緩存大小,須要清理的緩存大小 = 總緩存大小 - 預留緩存大小(memoryCapacity - preferredMemoryUsageAfterPurge),而後將緩存裏面的圖片按照插入時間排序,將最早插入的圖片依次刪除,若是刪除緩存大小大於須要清理的緩存大小,則結束。架構

5)內存緩存核心源碼解析
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier {
​    dispatch_barrier_async(self.synchronizationQueue, ^{
​        AFCachedImage *cacheImage = [[AFCachedImage alloc] initWithImage:image identifier:identifier];
​        // 第一步:若是identifier的緩存對象存在,要爲identifier賦予新的緩存對象,並將以前的緩存對象的內存減去
​        AFCachedImage *previousCachedImage = self.cachedImages[identifier];
​        if (previousCachedImage != nil) {
​            self.currentMemoryUsage -= previousCachedImage.totalBytes;
​        }
​        self.cachedImages[identifier] = cacheImage;
​        self.currentMemoryUsage += cacheImage.totalBytes;
​    });

​    dispatch_barrier_async(self.synchronizationQueue, ^{
​        if (self.currentMemoryUsage > self.memoryCapacity) {
​            /* 第二步:計算出將要移除的內存大小 */
​            UInt64 bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge;
						/*第三步: 將全部的緩存按最後使用時間從遠到近排列,而後依次刪除,每次刪除時都累加刪除的緩存的內存大小,累加的內存大於等於要移除的內存大小時中止刪除。*/
​            NSMutableArray <AFCachedImage*> *sortedImages = [NSMutableArray arrayWithArray:self.cachedImages.allValues];

​            NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"lastAccessDate" ascending:YES];
​            [sortedImages sortUsingDescriptors:@[sortDescriptor]];
​            UInt64 bytesPurged = 0;
​            for (AFCachedImage *cachedImage in sortedImages) {
​                [self.cachedImages removeObjectForKey:cachedImage.identifier];
​                bytesPurged += cachedImage.totalBytes;
​                if (bytesPurged >= bytesToPurge) {
​                    break ;
​                }
​            }
​            /* 第四步:更新當前使用的緩存內存大小 */
​            self.currentMemoryUsage -= bytesPurged;
​        }
​    });
}
複製代碼

3.3 AFSecurityPolicy

首先介紹一下HTTPS:app

HTTPS協議中的加密是用共享密鑰加密與公開密鑰加密的混合加密。共享密鑰加密,加解密使用同一個密鑰,即對稱加密;公開密鑰加密,分爲公鑰與私鑰,公鑰加密公開使用,而私鑰則用於解密。HTTPS協議在交換密鑰時使用公開密鑰加密,在通訊報文交換的過程當中使用共享密鑰。首先使用公開密鑰加密的方式安全地交換將在稍後的共享密鑰加密中要使用的密鑰,在確保交換的密鑰時安全的前提下,再使用共享密鑰加密方式進行通信交互。

3.3.1 簡介

AFSecurityPolicy類只作了一件事,就是完成HTTPS認證,是對系統類庫<Security/Security.h>的進一步封裝;AFNetworking的默認證書認證流程是客戶端單項認證,假如須要雙向驗證,則服務器和客戶端都須要發送數字證書給對方驗證,須要用戶自行實現。

3.3.2 AFNetworking的單項認證流程

3.3.3 代碼詳細解析

AFSecurityPolicy的三種驗證模式:
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
​    AFSSLPinningModeNone,               //表明無條件信任服務器的證書
​    AFSSLPinningModePublicKey,          //表明會對服務器返回的證書中的PublicKey進行驗證
​    AFSSLPinningModeCertificate,        //表明會對服務器返回的證書同本地證書所有進行校驗
}

接下來聲明瞭四個屬性:
  SSLPinningMode:返回SSL Pinning的類型,默認的是AFSSLPinningModeNone;
  pinnedCertificates:保存着全部的可用作校驗的證書的集合,evaluateServerTrust:forDomain: 就會返回true,即經過校驗;
  allowInvalidCertificates:使用容許無效或過時的證書,默認是NO不容許;
  validatesDomainName:是否驗證證書中的域名domain,默認是YES;
複製代碼

3.3.4 流程圖

3.3.5 證書挑戰核心代碼解析

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
​    // 挑戰處理類型爲默認
​    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
​    __block NSURLCredential *credential = nil;
​    // 自定義的方法,用來如何應對服務器端的認證挑戰
​    if (self.sessionDidReceiveAuthenticationChallenge) {
​        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
​    } else {
​        // 判斷接收服務器挑戰的方法是不是信任證書
​        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
​            // 去驗證服務器端的證書是否安全,即HTTPS的單項認證,這是AF的默認處理的認證方式,其它的認證方式只能由咱們本身的block去實現
​            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
​                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
​                if (credential) {
​                    // 證書挑戰
​                    disposition = NSURLSessionAuthChallengeUseCredential;
​                } else {
​                    // 默認挑戰方式 (不提供證書)
​                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
​                }
​            } else {
​                // 取消挑戰 (取消鏈接)
​                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
​            }

​        } else {
​            // 默認挑戰方式 (不提供證書)
​            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
​        }
​    }
    
​    /* 完成挑戰,將信任憑證發送給服務器 */
​    if (completionHandler) {
​        completionHandler(disposition, credential);
​    }
}
複製代碼

3.3.6 證書認證的核心代碼解析

// 根據當前的安全策略,返回是否受信
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust

​                  forDomain:(NSString *)domain
{
​    /*
​     評估必須保證是一個有效的過程。
​     若是容許無效證書,可是沒有證書或者採用AFSSLPinningModeNone模式,其餘信息齊全的時候,這時候會被告知,這是一個無效的驗證過程。
​    */
​    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;
​    }
​    // 建立安全策略,若是須要對域名進行驗證,則建立附帶入參域名的 SSL 安全策略。 不然建立一個基於 X.509 的安全策略。
​    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);
​    // 在 AFSSLPinningModeNone,不會進行公鑰或者證書的認證。 只要確保服務器給的信任評估是有效的(可以獲取到CA根證書)。  或者,若是用戶設置容許無效證書,那麼也會直接返回經過。
​    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;
​        // 上面已經對 AFSSLPinningModeNone 作了出了,這裏直接當成默認的狀況。返回NO
​          case AFSSLPinningModeCertificate: {
​            // 驗證本地證書和服務器發過來的信任進行甄別。
​            // 這裏本地使用的證書 "pinnedCertificates" 可能有不少個,因而轉化成 CFData 放入數組。
​            NSMutableArray *pinnedCertificates = [NSMutableArray array];
​            for (NSData *certificateData in self.pinnedCertificates) {
​                [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;
​            }
​            // 獲取全部的服務器的證書鏈,注意這和 AnchorCertificates 是不相同的。

​            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
​            // 遍歷服務器的證書鏈
​            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
​                // 若是證書鏈中包含了本地的證書,說明 serverTrust 是有效的服務器信任憑證。返回YES
​                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;
}
複製代碼

做者簡介

簡介:閆名月,民生科技有限公司,用戶體驗技術部Firefly移動金融開發平臺iOS開發工程師。

相關文章
相關標籤/搜索