探祕AFNetworking

該文章屬於<簡書 — 劉小壯>原創,轉載請註明:

<簡書 — 劉小壯>git


封面圖

AFNetworking源碼分析

AFNetworkingiOS最經常使用的網絡框架,雖然系統也有NSURLSession,可是咱們通常不會直接用它。AFNetworking通過了三個大版本,如今用的大多數都是3.x的版本。github

AFNetworking經歷了下面三個階段的發展:json

  • 1.0版本 : 基於NSURLConnection的封裝。
  • 2.0版本 : 兩套實現,分別基於NSURLConnectionNSURLSession,是轉向NSURLSession的過渡版。
  • 3.0版本 : 基於NSURLSession的封裝。

文件構成

文件構成

AFNetworking3.X的構成很簡單,主要就四部分,除此以外還有一些基於UIKitCategory,但這些並非標配。數組

  • Manager : 負責處理網絡請求的兩個Manager,主要實現都在AFURLSessionManager中。
  • Reachability : 網絡狀態監控。
  • Security : 處理網絡安全和HTTPS相關的。
  • Serialization : 請求和返回數據的格式化器。

AFURLSessionManager

AFN3.0中,網絡請求的manager主要有AFHTTPSessionManagerAFURLSessionManager構成,兩者爲父子關係。這兩個類職責劃分很清晰,父類負責處理一些基礎的網絡請求代碼,而且接受NSURLRequest對象,而子類則負責處理和http協議有關的邏輯。緩存

AFN的這套設計很便於擴展,若是之後想增長FTP協議的處理,則基於AFURLSessionManager建立子類便可。子類中只須要進行不多的代碼處理,建立一個NSURLRequest對象後調用父類代碼,由父類去完成具體的請求操做。安全

建立sessionManager

AFHTTPSessionManager類的初始化方法中並無太多實現代碼,其內部調用的都是父類AFURLSessionManagerinitWithSessionConfiguration方法,下面是此方法內部的一些關鍵代碼。服務器

在初始化方法中包含一個參數sessionConfiguration,若是沒有傳入的話默認是使用系統的defaultConfiguration,咱們建立是通常都不會自定義configuration,因此大多數都是系統的。網絡

if (!configuration) {
    configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}

隨後是NSURLSession的初始化代碼,關於NSOperationQueue後面詳細進行講解。NSURLSession的初始化方式有兩種,一種是使用系統的共享session,另外一種是本身建立sessionAFN選擇的是建立本身的session,而且每一個請求都會建立一個獨立的sessionsession

能夠經過NSURLSession進行鏈接複用,這樣能夠避免不少握手和揮手的過程,提升網絡請求速度,蘋果容許iOS設備上一個域名能夠有四個鏈接同時存在。可是因爲AFN的實現是每一個請求都建立一個session,因此就不能進行鏈接複用。多線程

因此能夠經過在外面對AFN進行二次封裝,將AFHTTPSessionManager複用爲單例對象,經過複用sessionManager的方式,來進行鏈接的複用。可是這種方案對於不一樣的requestSerializerresponseSerializer等狀況,仍是要作特殊兼容,因此最好創建一個sessionManager池,對於同類型的sessionManager直接拿出來複用,不然就建立新的。

// 共享session鏈接池
[NSURLSession sharedSession];
// 建立新session,則不能使用共享session鏈接池
[NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

因爲當前AFURLSessionManager對象的全部sessionTask請求任務,都是共享同一個回調代理的,因此AFN爲了區分每一個sessionTask,經過下面的可變字典,將全部taskDelegatetask.taskIdentifier的進行了一一對應,以便於很容易的對每一個請求task進行操做。

self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

在初始化方法中,能夠發現AFN在建立session後,調用了getTasksWithCompletionHandler方法來獲取當前全部的task。可是如今剛建立session,理論上來講是不該該有task的。但從AFNissues中找到了答案issues 3499

這是由於,在completionHandler回調中,爲了防止進入前臺時,經過session id恢復的task致使一些崩潰問題,因此這裏將以前的task進行遍歷,並將回調都置nil

[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
    for (NSURLSessionDataTask *task in dataTasks) {
        [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
    }

    for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
        [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
    }

    for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
        [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
    }
}];

建立task

AFURLSessionManager中進行task的建立,task的類型總共分爲三種,dataTaskuploadTaskdownloadTaskAFN並無對streamTask進行處理。

AFHTTPSessionManager在建立GETPOST等請求時,本質上都是調用了下面的方法或其相似的方法,方法內部會建立一個task對象,並調用addDelegateForDataTask將後面的處理交給AFURLSessionManagerTaskDelegate來完成。隨後會將task返回給調用方,調用方獲取到task對象後,也就是子類AFHTTPSessionManager,會調用resume方法開始請求。

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {

    __block NSURLSessionDataTask *dataTask = nil;
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

除了普通請求外,uploaddownload都有相似的處理。

相關API

addDelegateForDataTask方法中,會調用sessionManagersetDelegate:forTask:方法,此方法內部將tasktaskDelegate進行了註冊。因爲AFN能夠經過通知讓外界監聽請求狀態,因此在此方法中還監聽了taskresumesuspend事件,並在實現代碼中將事件廣播出去。

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
              downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    [self setDelegate:delegate forTask:dataTask];

    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

若是從AFHTTPSessionManager的建立任務開始,按代碼邏輯跟到這裏,發現其實AFN3.0的請求代碼真的很簡單,主要都集中在建立NSMutableURLRequest那裏,其餘都依賴於NSURLSession,由於確實NSURLSessionAPI封裝程度比較好,也很好使用。

AFN3.0的做用就是對NSURLSession的封裝性比較好,你不用去寫太多重複性的代碼,而且能夠很容易的經過block獲得回調結果。

AFURLSessionManagerTaskDelegate

NSURLSession的回調方法比較多,這裏只針對一些關鍵代碼進行講解,以及梳理總體回調邏輯,不一一列舉每一個回調方法的做用,詳細源碼各位能夠直接下載AFN代碼查看。

AFURLSessionManager中,有一個AFURLSessionManagerTaskDelegate類比較重要,這個類和sessionTask是一一對應的,負責處理sessionTask請求的不少邏輯,NSURLSessionDelegate的回調基本都轉發給taskDelegate去處理了。在NSURLSession回調中處理了HTTPS證書驗證、下載進度之類的,沒有太複雜的處理。

taskDelegate的設計很不錯,能夠將代理回調任務處理對象化,也能夠給AFURLSessionManager類瘦身。比較理想的是直接將代理設置爲taskDelegate,可是因爲會涉及一些AFURLSessionManager自身的處理邏輯,因此才設計爲消息傳遞的方式。

taskDelegate的功能很簡單,主要是NSData數據的處理,NSProgress上傳下載進度的處理,以及通知參數的處理。在進行AFN的下載處理時,NSData的數據拼接、事件回調,及文件處理,都是由taskDelegate來完成的。

下面是downloadTask任務完成時的處理代碼,其餘回調代碼就不一一列舉了。

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    self.downloadFileURL = nil;

    if (self.downloadTaskDidFinishDownloading) {
        self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (self.downloadFileURL) {
            NSError *fileManagerError = nil;

            if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
            }
        }
    }
}

taskDelegate中有一個很好的設計,taskDelegate並不直接在NSURLSession的代理方法中作進度拼接和回調。而是對於上傳和下載任務分別對應不一樣的NSProgress,並經過KVO來監聽fractionCompleted屬性,而且實現cancelsuspend等狀態回調。任務的狀態和進度處理交給NSProgress,在回調方法中直接拼接NSProgress的進度,從而回調KVO方法。

NSProgress內部的cancelpauseresume方法,正好能夠對應到sessionTask的方法調用。可是從代碼角度來看,AFN好像並無進行相關的調用,但這個設計思路很好。

_uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
_downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
    
__weak __typeof__(task) weakTask = task;
for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ])
{
    progress.totalUnitCount = NSURLSessionTransferSizeUnknown;
    progress.cancellable = YES;
    progress.cancellationHandler = ^{
        [weakTask cancel];
    };
    progress.pausable = YES;
    progress.pausingHandler = ^{
        [weakTask suspend];
    };
#if AF_CAN_USE_AT_AVAILABLE
    if (@available(iOS 9, macOS 10.11, *))
#else
    if ([progress respondsToSelector:@selector(setResumingHandler:)])
#endif
    {
        progress.resumingHandler = ^{
            [weakTask resume];
        };
    }
    
    [progress addObserver:self
               forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                  options:NSKeyValueObservingOptionNew
                  context:NULL];
}

_AFURLSessionTaskSwizzling

看過源碼的話,能夠發現AFURLSessionManager中還有一個_AFURLSessionTaskSwizzling類,這裏咱們簡稱taskSwizzling類。我認爲此類的設計實在是冗餘,此類的主要功能就是在+load方法中進行一個swizzling,將dataTaskresumesuspend方法進行替換,而且在替換後的方法中發出對應的通知,並無太多實際的功能。

只不過taskSwizzling類中仍是有一些不錯的代碼設計值得借鑑的,因爲sessionTask存在一系列繼承鏈,因此直接對其進行swizzling對其餘子類並不生效,由於每一個子類都有本身的實現,而寫一大堆swizzling又沒有什麼技術含量。

iOS7iOS8上,sessionTask的繼承關係並不同,最好進行一個統一的處理。AFN採起的方式是建立一個dataTask對象,並對這個對象進行swizzling,而且遍歷其繼承鏈一直進行swizzling,這樣保證集成繼承鏈的正確性。

NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
    
while (class_getInstanceMethod(currentClass, @selector(resume))) {
    Class superClass = [currentClass superclass];
    IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
    IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
    if (classResumeIMP != superclassResumeIMP &&
        originalAFResumeIMP != classResumeIMP) {
        [self swizzleResumeAndSuspendMethodForClass:currentClass];
    }
    currentClass = [currentClass superclass];
}

+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
    Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
    Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));

    if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
        af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
    }

    if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
        af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
    }
}

clang預編譯指令

AFN爲了不發生編譯器警告,採起了預編譯指令對代碼進行修飾,預編譯指令基本由三部分組成,pushpopignored類型。Github上有人維護了一份clang warning清單,若是想進行對應的預編譯處理能夠上去找找有沒有合適的。

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop

線程問題

NSURLSessioniOS8如下會併發建立多個task,但併發設置task identifier的時候會存在identifier重複的問題。爲了解決這個問題,在iOS8如下,系統將全部sessionTask的建立都放在一個同步的串行隊列中進行,保證建立及賦值操做是串行進行的。

url_session_manager_create_task_safely(^{
    dataTask = [self.session dataTaskWithRequest:request];
});

url_session_manager_create_task_safely(^{
    uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
});

// 若是Foundation版本小於iOS8,則把block任務放在一個同步隊列中執行。這個問題是因爲在iOS8如下併發建立任務,可能會有多個相同的identifier
static void url_session_manager_create_task_safely(dispatch_block_t block) {
    if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
        dispatch_sync(url_session_manager_creation_queue(), block);
    } else {
        block();
    }
}

一個比較有意思的是,AFN爲了讓開發者明白爲何要加這個判斷,對iOS8系統的判判定義成了一個宏,而且用Apple Supportid做爲宏定義命名,很見名知意。

#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0

AFN在回調didCompleteWithError方法,並處理返回數據時,會切換到其餘線程和group去處理,處理完成後再切換到主線程並通知調用方。

AFN提供了兩個屬性,用來設置請求結束後進行回調的dispatch queuedispatch group,若是不設置的話,AFN會有默認的實現來處理請求結束的操做。下面是groupqueue的實現,AFN對於返回數據的處理,採用的是併發處理。

static dispatch_queue_t url_session_manager_processing_queue() {
    static dispatch_queue_t af_url_session_manager_processing_queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT);
    });

    return af_url_session_manager_processing_queue;
}

static dispatch_group_t url_session_manager_completion_group() {
    static dispatch_group_t af_url_session_manager_completion_group;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        af_url_session_manager_completion_group = dispatch_group_create();
    });

    return af_url_session_manager_completion_group;
}

NSOperationQueue

AFN在建立AFURLSessionManageroperationQueue時,將其最大併發數設置爲1。這是由於在建立NSURLSSession時,蘋果要求網絡請求回來的數據順序執行,爲了保證代理方法的執行順序,因此須要串行的調用NSURLSSession的代理方法。

AFHTTPSessionManager

AFHTTPSessionManager本質上是對父類AFURLSessionManager的封裝,主要實現都在父類中,本身內部代碼實現很簡單。在建立AFHTTPSessionManager時會傳入一個baseURL,以及指定requestSerializerresponseSerializer對象。

從代碼實現來看,AFN的請求並非單例形式的,每一個請求都會建立一個新的請求對象。平時調用的GETPOST等網絡請求方法,都定義在AFHTTPSessionManager中。AFHTTPSessionManager內部則調用父類方法,發起響應的請求並獲取到task對象,調用taskresume後返回給調用方。

- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                     progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                      success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                      failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{

    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];

    [dataTask resume];

    return dataTask;
}

AFURLRequestSerialization

AFURLRequestSerialization負責建立NSMutableURLRequest請求對象,並對request進行請求的參數拼接、設置緩存策略、設置請求頭等關於請求相關的配置。

AFURLRequestSerialization並非一個類,而是一個文件,其中包含三個requestSerializer請求對象,分別對應着不一樣的請求序列化器。

  • AFHTTPRequestSerializer:普通請求。
  • AFJSONRequestSerializer:JSON請求。
  • AFPropertyListRequestSerializer:一種特殊的xml格式請求。

這三個類區別就在於Content-Type不一樣,其餘基本都是同樣的。AFN默認是HTTP的。

AFURLRequestSerialization協議

在文件中定義了同名的AFURLRequestSerialization協議,不一樣的requestSerializer會對協議方法有不一樣的實現,下面是AFHTTPRequestSerializer的實現代碼。其核心代碼實現也比較直觀,就是在建立requestSerializer的時候,設置請求頭的公共參數,以及將請求參數經過NSJSONSerialization轉換爲NSData,並將其賦值給request對象的httpBody,下面是精簡後的核心代碼。

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    if (parameters) {
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        }

        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error];
        [mutableRequest setHTTPBody:jsonData];
    }

    return mutableRequest;
}

若是想給網絡請求設置請求參數的話,須要經過requestSerializer對外暴露的API添加參數,AFNrequestManager並不直接對外提供設置請求頭的代碼。經過requestSerializer能夠對請求頭進行添加和刪除、以及清空的操做。

從建立AFURLRequestSerialization對象到最後返回NSURLRequest對象,中間的過程並不複雜,主要是設置請求頭和拼接參數,邏輯很清晰。

AFQueryStringPair

AFURLRequestSerialization有一個很重要的功能就是參數處理,AFQueryStringPair就是負責處理這些參數的。pair類中定義了兩個屬性,分別對應請求參數的keyvalue。除此以外,還定義了一些很是實用的C語言函數。

@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;

- (id)initWithField:(id)field value:(id)value;

- (NSString *)URLEncodedStringValue;
@end

AFQueryStringFromParameters函數負責將請求參數字典,轉成拼接在URL後面的參數字符串,這個函數是AFQueryStringPair類中定義的一個關鍵函數。函數內部經過AFQueryStringPairsFromDictionary函數將參數字典,轉爲存儲pair對象的數組並進行遍歷,遍歷後調用URLEncodedStringValue方法對參數進行拼接,最後成爲字符串參數。

URLEncodedStringValue方法實現很簡單,就是進行一個keyvalue的拼接,而且在中間加上「=」。

static NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }

    return [mutablePairs componentsJoinedByString:@"&"];
}

- (NSString *)URLEncodedStringValue {
    if (!self.value || [self.value isEqual:[NSNull null]]) {
        return AFPercentEscapedStringFromString([self.field description]);
    } else {
        return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
    }
}

下面是參數拼接的代碼,函數內部會將原有的參數,轉換爲AFQueryStringPair對象的類型,但以前的層級結構不變。這句話是什麼意思呢,就是說對原有傳入的對象進行逐層遞歸調用,而且將最後一層字典的keyvalue參數,轉成pair類型的對象,而且將嵌套有pair對象的數組返回給調用方。

對象層級不變,但字典、集合都會被轉換爲數組結構,也就是以前傳入字典、數組、字典的嵌套結構,返回的時候就是數組、數組、pair的結構返回。

NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}

NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    if ([value isKindOfClass:[NSDictionary class]]) {
        NSDictionary *dictionary = value;
        for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            id nestedValue = dictionary[nestedKey];
            if (nestedValue) {
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
            }
        }
    } else if ([value isKindOfClass:[NSArray class]]) {
        NSArray *array = value;
        for (id nestedValue in array) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
        }
    } else if ([value isKindOfClass:[NSSet class]]) {
        NSSet *set = value;
        for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
        }
    } else {
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    return mutableQueryStringComponents;
}

設置NSMutableURLRequest

AFHTTPRequestSerializer在建立NSMutableURLRequest時,須要爲request設置屬性。serializer對外提供了和request同名的一些屬性,外界直接調用serializer便可設置request的屬性。

AFHTTPRequestSerializer內部建立request時,並非根據設置request的屬性按個賦值,而是經過一個屬性數組AFHTTPRequestSerializerObservedKeyPaths,將serializer須要賦值給request的屬性,都放在數組中。

static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
    static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
    });

    return _AFHTTPRequestSerializerObservedKeyPaths;
}

在初始化AFHTTPRequestSerializer時,遍歷keyPath數組並經過KVO的方式,監聽serializer的賦值。若是外界對serializer對應的屬性進行賦值,則將其添加到mutableObservedChangedKeyPaths數組中。在建立request對象是,遍歷mutableObservedChangedKeyPaths數組並將值賦值給request對象。

for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
    if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
        [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
    }
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(__unused id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (context == AFHTTPRequestSerializerObserverContext) {
        if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
            [self.mutableObservedChangedKeyPaths removeObject:keyPath];
        } else {
            [self.mutableObservedChangedKeyPaths addObject:keyPath];
        }
    }
}
    
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
    if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
        [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
    }
}

表單提交

當進行POST表單提交時,須要用到AFMultipartFormData協議。調用POST方法後,會回調一個遵照此協議的對象,能夠經過此對象進行表單提交操做。

[manager POST:requestURL parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData>  _Nonnull formData) {
    [formData appendPartWithFileData:params[@"front_img"]
                                name:@"front_img"
                            fileName:frontImgfileName
                            mimeType:@"multipart/form-data"];
    [formData appendPartWithFileData:params[@"reverse_img"]
                                name:@"reverse_img"
                            fileName:reverseImgfileName
                            mimeType:@"multipart/form-data"];
    [formData appendPartWithFileData:params[@"face_img"]
                                name:@"face_img"
                            fileName:faceImgfileName
                            mimeType:@"multipart/form-data"];

} progress:^(NSProgress * _Nonnull uploadProgress) {
    // nothing
} success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    // nothing
} failure:nil];

進行表單提交時,能夠直接傳入文件,也能夠傳入路徑。表單提交能夠同時提交多個文件,理論上數量不受限制。

緩存策略

AFN的緩存策略和NSURLCache的緩存策略一致,而且直接使用系統的枚舉,這對iOS開發者是很是友好的。下面是枚舉定義,忽略掉一些unimplemented的,和一些重定向到已有枚舉的,可用的都在這。

typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
    NSURLRequestUseProtocolCachePolicy = 0,
    NSURLRequestReloadIgnoringLocalCacheData = 1,
    NSURLRequestReturnCacheDataElseLoad = 2,
    NSURLRequestReturnCacheDataDontLoad = 3,
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,
    NSURLRequestReloadRevalidatingCacheData = 5,
};
  • NSURLRequestUseProtocolCachePolicy,使用協議指定的緩存策略。
  • NSURLRequestReloadIgnoringLocalCacheData,忽略緩存,直接發起請求。
  • NSURLRequestReturnCacheDataElseLoad,不驗證緩存過時時間,若是有則使用緩存數據,若是不存在則請求服務器。
  • NSURLRequestReturnCacheDataDontLoad,不驗證緩存過時時間,若是有則使用緩存數據,若是不存在則請求失敗。
  • NSURLRequestReloadIgnoringLocalAndRemoteCacheData,忽略本地緩存,以及代理等中間介質的緩存。
  • NSURLRequestReloadRevalidatingCacheData,和數據的源服務器驗證數據合法性,若是能夠用就直接使用緩存數據,不然從服務器請求數據。

AFURLResponseSerialization

AFURLResponseSerialization負責處理response相關的邏輯,其功能主要是設置acceptType、編碼格式和處理服務器返回數據。一樣的,AFURLResponseSerialization也有同名的協議,每一個子類都遵循代理方法並實現不一樣的返回值處理代碼。

- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error;

AFURLRequestSerialization同樣,AFURLResponseSerialization由一個父類和六個子類構成,子類中有一個是Mac的,因此這裏不作分析,子類的職責只是對acceptType作修改以及處理具體的返回數據。

  • AFHTTPResponseSerializer:公共父類,處理返回值類型爲NSData二進制。
  • AFJSONResponseSerializer:JSON返回數據,也是默認類型。
  • AFXMLParserResponseSerializer,處理XML返回數據,由系統NSXMLParser負責處理。
  • AFPropertyListResponseSerializer:處理特殊XML返回數據,也就是plist數據。
  • AFImageResponseSerializer:處理圖片返回數據,這個類型用的也比較多。
  • AFCompoundResponseSerializer:處理複雜數據,返回結果類型有多種。

容錯處理

因爲服務器有時候會返回null的狀況,系統會將其轉換爲NSNull對象,而對NSNull對象發送不正確的消息,就會致使崩潰。從服務器接收到返回值後,AFN會對返回值進行一個遞歸查找,找到全部NSNull對象並將其移除,防止出現向NSNull對象發送消息致使的崩潰。

static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
    if ([JSONObject isKindOfClass:[NSArray class]]) {
        NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
        for (id value in (NSArray *)JSONObject) {
            [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
        }

        return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
    } else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
        NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
        for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
            id value = (NSDictionary *)JSONObject[key];
            if (!value || [value isEqual:[NSNull null]]) {
                [mutableDictionary removeObjectForKey:key];
            } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
                mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
            }
        }

        return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
    }

    return JSONObject;
}

AFNetworking的設計技巧

bundleForClass

在使用NSBundle對象時,咱們最經常使用的就是mainBundle或者bundleWithPath這種方式獲取bundle,這種對於都是從app二進制讀取的時候是沒有問題的。可是若是涉及到framework動態庫,就不是那麼易於使用。

framework中能夠包含資源文件,例如.bundle文件。若是是動態庫形式的framework(framework也有靜態形式),其會以一個獨立二進制的形式表現,而且會分配獨立的二進制空間。在讀取bundle的時候,就能夠考慮使用bundleForClass的方式讀取。

bundleForClass表示從當前類定義的二進制,所在的程序包中讀取NSBundle文件。例如.app就是從main bundle中讀取,若是是framework就從其所在的二進制中讀取。

網絡指示器

AFN提供了一些UIKitCategory,例如網絡請求發起時,網絡指示器轉菊花,則由AFNetworkActivityIndicatorManager類負責。開啓網絡指示器很簡單,添加下面代碼便可,網絡指示器默認是關閉的。

[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];

這裏不對AFNetworkActivityIndicatorManager的代碼進行過多的分析,只是調其中比較重要的點來分析,下面統稱爲indicatorManager

以前在_AFURLSessionTaskSwizzling類中寫了不少代碼,就是爲了發出resumesuspend兩個通知,這兩個通知在indicatorManager中就用到了。網絡指示器監聽了下面的三個通知,而且徹底由通知來驅動。

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil];

若是看indicatorManager中的源碼,你會發現爲何裏面還有timer,徹底不須要啊,有網絡請求就轉菊花,沒網絡請求就中止不就好了嗎?

這是由於AFN考慮,若是一個網絡請求很快的話,會致使菊花出現轉一下很快就消失的狀況,若是網絡請求比較多會屢次閃現。因此對於這個問題,indicatorManager經過Timer的方式實現,若是在指定的區間內網絡請求已經結束,則不在顯示菊花,若是有屢次請求則在請求之間也不進行中斷。

對於開始轉圈設置的是1.0秒,結束轉圈設置的是0.17秒。也就是當菊花開始旋轉時,須要有1.0秒的延時,這個時間足以保證以前的菊花中止轉動。結束轉圈則會在0.17秒以後進行,能夠保證菊花的旋轉至少會有0.17秒。

static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0;
static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17;

- (void)startActivationDelayTimer {
    self.activationDelayTimer = [NSTimer
                                 timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO];
    [[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];
}

- (void)startCompletionDelayTimer {
    [self.completionDelayTimer invalidate];
    self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO];
    [[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes];
}

因爲indicatorManager是採用通知的方式進行回調,全部的網絡請求通知都會調到這。因此當多個網絡請求到來時,會經過一個_activityCount來進行計數,能夠將其理解爲一個隊列,這樣更容易理解。在顯示網絡指示器的時候,就是基於_activityCount來進行判斷的,若是隊列中有請求則顯示網絡指示器,不管有多少請求。

這種設計思路比較好,在項目中不少地方均可以用到。例若有些方法須要成對進行調用,例如播放開始和暫停,若是某一個方法調用屢次就會形成bug。這種方式就比較適合用count的方式進行容錯,內部針對count作一些判斷操做。

- (void)incrementActivityCount {
    [self willChangeValueForKey:@"activityCount"];
    @synchronized(self) {
        _activityCount++;
    }
    [self didChangeValueForKey:@"activityCount"];

    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateCurrentStateForNetworkActivityChange];
    });
}

- (void)decrementActivityCount {
    [self willChangeValueForKey:@"activityCount"];
    @synchronized(self) {
        _activityCount = MAX(_activityCount - 1, 0);
    }
    [self didChangeValueForKey:@"activityCount"];

    dispatch_async(dispatch_get_main_queue(), ^{
        [self updateCurrentStateForNetworkActivityChange];
    });
}

indicatorManager是多線程安全的,在一些關鍵地方都經過synchronized的方式加鎖,防止從各個線程調用過來的通知形成資源搶奪的問題。

AFSecurityPolicy

驗證處理

AFN支持https請求,並經過AFSecurityPolicy類來處理https證書及驗證,但其https請求的執行仍是交給NSURLSession去完成的。

下面是NSURLSession的一個代理方法,當須要進行證書驗證時,能夠重寫此方法並進行自定義的驗證處理。驗證完成後經過completionHandlerblock來告知處理結果,而且將驗證結果disposition和公鑰credential傳入。

AFN經過AFSecurityPolicy類提供了驗證邏輯,而且在內部能夠進行證書的管理。也能夠不使用AFN提供的驗證邏輯,重寫sessionDidReceiveAuthenticationChallengeblock便可自定義驗證邏輯,不走AFN的邏輯。

- (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]) {
            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);
    }
}

除了進行NSURLSession請求驗證的回調,對於每一個task也有對應的代理方法。兩個代理方法內部實現基本同樣,區別在於對於每一個taskAFN提供了taskDidReceiveAuthenticationChallenge回調block,能夠由外界自定義證書驗證過程。

驗證結果是經過一個枚舉回調給NSURLSession的,參數是一個NSURLSessionAuthChallengeDisposition類型的枚舉,表示證書驗證的狀況,此枚舉包含下面幾個具體值。

  • NSURLSessionAuthChallengeUseCredential

使用當前證書創建SSL鏈接,並處理後續請求

  • NSURLSessionAuthChallengePerformDefaultHandling

使用默認的處理方式,當前證書被忽略

  • NSURLSessionAuthChallengeCancelAuthenticationChallenge

驗證不經過,取消整個網絡請求

  • NSURLSessionAuthChallengeRejectProtectionSpace

此次驗證被忽略,但不取消網絡請求

Security

HTTPS請求的密鑰管理等安全相關的處理,都放在Security.framework框架中。在AFSecurityPolicy中常常能夠看到SecTrustRef類型的變量,其表示的就是密鑰對象,其中包含了公鑰等信息。

咱們能夠經過下面的命令獲取到公鑰,具體格式這裏不作過多介紹,詳細的能夠Google一下公鑰格式。

// 獲取公鑰命令
SecTrustCopyPublicKey(serverTrust)

// 打印的公鑰(公鑰已作脫敏)
<SecKeyRef algorithm id: 1, key type: RSAPublicKey, version: 4, block size: 2048 bits, exponent: {hex: 10001, decimal: 65537}, modulus: A51E89C5FFF2748A6F70C4D701D29A39723C3BE495CABC5487B05023D957CD287839F6B5E53F90B963438417547A369BBA5818D018B0E98F2449442DFBD7F405E18D5A827B6F6F0A5A5E5585C6C0F342DDE727681902021B7A7CE0947EFCFDDC4CCF8E100D94A454156FD3E457F4719E3C6B9E408CD4316B976A6C44BD91A057FEA4A115BEB1FE28E71005D2198E3B79B8942779B434A0B08F82B3D390A7B94B958BB71D9B69564C84A1B03FE22D4C4E24BBA2D5ED3F660F1EBC757EF0E52A7CF13A8C167B0E6171B2CD6678CC02EAF1E59147F53B3671C33107D9ED5238CBE33DB617931FFB44DE70043B2A618D8F43608D6F494360FFDEE83AD1DCE120D6F1, addr: 0x280396dc0>

AFSecurityPolicy概述

AFSecurityPolicy的職責比較單一,只處理公鑰和驗證的邏輯,其定義是一個單例對象。此類主要由四個屬性和一個方法構成。

// 證書驗證方式
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
// 本地自簽名證書集合
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
// 是否驗證證書的合法性(是否容許自簽名證書)
@property (nonatomic, assign) BOOL allowInvalidCertificates;
// 是否驗證域名是否有效
@property (nonatomic, assign) BOOL validatesDomainName;

若是進行細分的話,AFSecurityPolicy的功能基本就兩個。一個是經過CA的方式進行驗證,另外一個就是進行SSL Pinning自簽名驗證。evaluateServerTrust:forDomain:AFSecurityPolicy最主要的方法,用來進行證書的合法性驗證。

SSL Pinning

AFSecurityPolicy進行SSL Pinning驗證的方式分爲如下三種,若是是None則會執行正常CA驗證的流程,其餘兩種都是自簽名的流程。AFN中默認的調用是defaultPolicy方法,其內部設置的是AFSSLPinningModeNone模式。

  • AFSSLPinningModeNone

正常流程,經過CA機構頒發的公鑰,對服務器下發的證書驗證數字簽名,而且得到公鑰。

  • AFSSLPinningModeCertificate

不經過CA的流程進行驗證,而是經過本地內置的服務端證書進行驗證,驗證過程分爲兩步。首先驗證證書是否過時或失效,其次驗證本地是否包含此證書。

  • AFSSLPinningModePublicKey

不進行CA的驗證,也不驗證證書,只驗證公鑰是否有效。

對於本地自簽名證書的管理有兩種方式,一種是默認會在本地查找遍歷全部.cer的文件,並存在一個自簽名證書的集合中。也能夠在建立AFSecurityPolicy對象時傳入SSLPinningMode,下面是查找本地.cer文件的邏輯。

+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {
    NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];

    NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]];
    for (NSString *path in paths) {
        NSData *certificateData = [NSData dataWithContentsOfFile:path];
        [certificates addObject:certificateData];
    }

    return [NSSet setWithSet:certificates];
}

自簽名證書

HTTPS在進行握手時,須要經過CA的公鑰進行驗證,保證服務器公鑰的合法性,沒有被篡改成有問題的公鑰。若是使用CA機構頒發的證書,不管使用NSURLSession仍是AFNetworking都不須要修改代碼,這些都會自動完成。若是不想使用CA的證書驗證,例如自簽名證書在CA證書驗證時就會失敗。

這種狀況可使用自簽名的方式進行驗證,也就是在客戶端本地內置一份證書,服務器進行四次握手時,經過保存在本地的證書和服務器進行對比,證書相同則表示驗證成功,不走CA的驗證方式。

AFN爲咱們提供了自簽名證書的驗證方法,經過SSLPinningMode設置驗證方式爲自簽名,而且傳入證書集合。若是沒有傳入證書集合,則AFN默認會遍歷整個沙盒,查找全部.cer的證書。

進行沙盒驗證時,須要將AFSecurityPolicyallowInvalidCertificates設置爲YES,默認是NO,表示容許無效的證書,也就是自簽名的證書。

NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"12306" ofType:@"cer"];
NSData *certData = [NSData dataWithContentsOfFile:cerPath];
NSSet *set = [NSSet setWithObject:certData];

AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set];
securityPolicy.allowInvalidCertificates = YES;

AFNetworking 2.x

AFNetworking2.xNSURLSessionNSURLConnection兩部分組成,而且分別對應不一樣的類,這裏主要介紹NSURLConnection部分的源碼實現。

相關類

整體概覽

NSURLConnection實現由下面三個類構成,從源碼構成能夠看出,不管是session仍是connection方案,都具備很好的擴展性。例如這裏AFHTTPRequestOperation是基於AFURLConnectionOperation實現的,若是須要實現FTP協議,則能夠建立一個繼承自AFURLConnectionOperationAFFPTConnectionOperation類並重寫對應方法便可。

  • AFURLConnectionOperation
    繼承自NSOperation,負責網絡請求的邏輯實現,每一個網絡請求就是一個Operation對象。
  • AFHTTPRequestOperation
    繼承自AFURLConnectionOperation,處理HTTP相關網絡請求。
  • AFHTTPRequestOperationManager
    內部持有一個NSOperationQueue,負責管理全部Operation網絡請求。

AFURLConnectionOperation

下面是AFURLConnectionOperation的初始化方法,和AFURLSessionManager有些不同。其內部增長了狀態的概念,以及RunloopMode的概念,這兩個咱們後面會詳細講解。shouldUseCredentialStorage表示是否由系統作證書驗證,後面設置了securityPolicy,和sessionManager同樣也是使用默認方案。

- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
    _state = AFOperationReadyState;

    self.lock = [[NSRecursiveLock alloc] init];
    self.lock.name = kAFNetworkingLockName;
    self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
    self.request = urlRequest;
    self.shouldUseCredentialStorage = YES;
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];
}

AFURLConnectionOperation繼承自NSOperation,因爲NSOperation和網絡請求的過程很像,有開始、暫停、完成等,而且很好的支持KVO監聽,因此AFN將每一個網絡請求都當作一個Operation任務。AFURLConnectionOperation能夠設置任務優先級,也能夠經過AFHTTPRequestOperationManager設置最大併發數,基本上NSOperationQueue提供的功能都能用。

AFHTTPRequestOperationManager中定義了NSOperationQueue,建立網絡請求任務後,都會被加入到queue中,隨後由系統調用queue中的operation任務,執行operationstart方法發起請求。AFURLConnectionOperation只須要在內部實現startpauseresume等父類方法便可,其餘都由系統去進行調用。這種設計能夠很好的將manageroperation進行解耦,兩者不用直接發生調用關係。

NSURLConnection中,每一個網絡請求對應一個AFHTTPRequestOperation,全部網絡請求都共用一個manager來管理operation。而AFHTTPSessionManager則不一樣,每一個網絡請求對應一個manager以及一個task

state設計

AFURLConnectionOperation支持KVO的方式,讓外界監聽網絡請求的變化,並經過重寫setState方法,在內部加入willChangeValueForKey觸發KVO回調。AFN經過AFOperationState來管理網絡請求狀態,下面是AFN對其的狀態定義。

  • AFOperationPausedState
    請求暫停
  • AFOperationReadyState
    請求已準備好
  • AFOperationExecutingState
    請求正在執行中
  • AFOperationFinishedState
    請求完成

當網絡請求狀態發生改變時,都會調用setState方法進行賦值,例以下面是請求完成時的處理代碼。除此以外,當判斷AFN請求狀態時,也是經過這個屬性做爲判斷依據的。

- (void)finish {
    [self.lock lock];
    self.state = AFOperationFinishedState;
    [self.lock unlock];
}

常駐線程

AFURLConnectionOperation中設計了常駐線程,而且重寫了operationstart等方法,網絡請求的startcancelpause等操做,都是在常駐線程中完成的。網絡請求結束後,數據回調的處理也是在這個線程中完成的。

這是由於在哪一個線程建立NSURLConnection對象併發出請求,則數據返回時也默認從那個線程接受數據。若是請求都是從主線程發出的,請求返回時若是屏幕正在滑動,runloopModeUITrackingRunLoopMode則不能處理返回數據。而若是把網絡請求都加到主線程的NSRunLoopCommonModes中,在大量網絡請求返回時,處理返回數據會影響屏幕滑動FPS

因此爲了保證網絡請求數據能夠正常返回並被處理,而又不影響屏幕FPS,則用一個單獨的線程來處理。若是每一個請求都對應一個線程來處理返回任務,會形成大量線程的佔用,因此用一個常駐線程來處理全部網絡請求,來保證線程資源的最小佔用。常駐線程其實是一個單例線程,而且這個單例線程被加入了一個Port進行保活,保證線程能夠不被退出。

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });

    return _networkRequestThread;
}

經過AFURLConnectionOperation發起網絡請求時,實際建立connection對象的代碼在下面的方法中。在建立connection對象後,並無當即發出網絡請求,而是將startImmediately設置爲NO。隨後會設置NSURLConnectionNSOutputStreamRunloopMode,網絡請求會從單例線程的runLoopModes中發出,這樣當網絡請求返回時,回調代碼也會在runLoopModes中去執行。

operationDidStart方法中會調用NSURLConnectionscheduleInRunLoop:forMode:方法,將網絡請求任務派發到Runloop指定的Mode中。我以爲給Operation設置runLoopModes其實意義不大,由於常駐線程基本上只會有一個Mode,也就是NSRunloopDefaultMode,基本上不會有其餘Mode,因此這裏設置runLoopModes沒什麼意義。

- (void)operationDidStart {
    self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];

    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    for (NSString *runLoopMode in self.runLoopModes) {
        [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
        [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
    }

    [self.outputStream open];
    [self.connection start];
}

代理方法

AFURLConnectionOperation是經過NSURLConnection實現網絡請求的,這裏簡單講一下operation中代理方法的實現。

AFN實現了https證書驗證的代碼,具體實現和AFURLSessionManager基本相似,而且也是經過AFSecurityPolicy來處理具體的證書驗證邏輯。

- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;

關於請求服務器數據這塊值得講一下,在NSURLConnection接收服務器數據時,AFN經過建立了一個outputStream,來承載和組織具體的數據,而且在內存中進行存儲。當沒有可用空間或發生其餘錯誤時,會經過streamError的方式進行體現。

當網絡請求結束時,會調用didFinishLoading方法,AFN會從outputStream中拿出數據並賦值給responseData,當作返回值數據使用。

- (void)connection:(NSURLConnection __unused *)connection
    didReceiveData:(NSData *)data
{
    NSUInteger length = [data length];
    while (YES) {
        NSInteger totalNumberOfBytesWritten = 0;
        if ([self.outputStream hasSpaceAvailable]) {
            const uint8_t *dataBuffer = (uint8_t *)[data bytes];

            NSInteger numberOfBytesWritten = 0;
            while (totalNumberOfBytesWritten < (NSInteger)length) {
                numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)];
                if (numberOfBytesWritten == -1) {
                    break;
                }

                totalNumberOfBytesWritten += numberOfBytesWritten;
            }

            break;
        } else {
            [self.connection cancel];
            if (self.outputStream.streamError) {
                [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
            }
            return;
        }
    }

    if (self.downloadProgress) {
        self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength);
    }
}

- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {
    self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];

    [self.outputStream close];
    if (self.responseData) {
       self.outputStream = nil;
    }

    self.connection = nil;

    [self finish];
}

outputStream,也會有與之對應的inputStreaminputStream實現很簡單,就是修改NSMutableURLRequestHTTPBodyStream

- (NSInputStream *)inputStream {
    return self.request.HTTPBodyStream;
}

- (void)setInputStream:(NSInputStream *)inputStream {
    NSMutableURLRequest *mutableRequest = [self.request mutableCopy];
    mutableRequest.HTTPBodyStream = inputStream;
    self.request = mutableRequest;
}

內存管理

在建立AFHTTPRequestOperation時會將successfailureblock傳給operation,而且在operation執行完成並回調completionBlock時,執行這兩個block代碼。可是因爲completionBlock中直接使用了self,致使了循環引用的問題。

- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id __nullable responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
      self.completionBlock = ^{
          // something code ...
      };
}

completionBlock的循環引用是AFN有意而爲之的,爲的就是保持operation的生命週期,以保證請求處理完成並接收返回的block回調。

對於循環引用的生命週期,AFN採起的是主動打破循環引用的方式,也就是重寫父類的completionBlock,而且在調用block結束後,主動將completionBlock賦值爲nil,從而主動打破循環引用。

- (void)setCompletionBlock:(void (^)(void))block {
    if (!block) {
        [super setCompletionBlock:nil];
    } else {
        __weak __typeof(self)weakSelf = self;
        [super setCompletionBlock:^ {
            __strong __typeof(weakSelf)strongSelf = weakSelf;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
            dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop

            dispatch_group_async(group, queue, ^{
                block();
            });

            dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
                [strongSelf setCompletionBlock:nil];
            });
        }];
    }
}

AFNetworkReachabilityManager

AFNetworking中還有很重要的一部分,就是Reachability,用來作網絡狀態監控的。AFNetworkingYYKit、蘋果官方都提供有ReachabilityAPI使用,內部實現原理基本差很少。

代碼實現也很簡單,主要依賴SystemConfiguration.framework框架的SCNetworkReachability,註冊一個Callback而後等着回調就能夠。這裏講一下核心邏輯,一些細枝末節的就忽略了。

Reachability提供了兩種初始化方法,一種是經過域名初始化的managerForDomain:方法,傳入一個域名,基於這個域名的訪問狀況來判斷當前網絡狀態。另外一種是經過地址初始化的managerForAddress:方法,建立一個sockaddr_in對象,並基於這個對象來判斷網絡狀態。

+ (instancetype)managerForAddress:(const void *)address {
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
    AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];

    CFRelease(reachability);
    
    return manager;
}

+ (instancetype)manager {
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_len = sizeof(address);
    address.sin_family = AF_INET;
    return [self managerForAddress:&address];
}

下面startMonitoring中是開啓網絡監測的核心代碼,主要邏輯是設置了兩個Callback,一個是block的一個是函數的,並添加到Runloop中開始監控。由此能夠推測,Reachability的代碼實現主要依賴Runloop的事件循環,而且在事件循環中判斷網絡狀態。

當網絡發生改變時,就會回調AFNetworkReachabilityCallback函數,回調有三個參數。targetSCNetworkReachabilityRef對象,flags是網絡狀態,info是咱們設置的block回調參數。回調Callback函數後,內部會經過block以及通知的形式,對外發出回調。

- (void)startMonitoring {
    [self stopMonitoring];

    if (!self.networkReachability) {
        return;
    }

    __weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;

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

    };

    SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
    SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
    SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
        SCNetworkReachabilityFlags flags;
        if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
            AFPostReachabilityStatusChange(flags, callback);
        }
    });
}

- (void)stopMonitoring {
    if (!self.networkReachability) {
        return;
    }

    SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}

static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
    AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}

AFNetworking總結

AFNetworking對請求數據的序列化,以及返回數據的反序列化作了不少處理。使開發者只須要傳入一個字典便可構建請求參數,無需處理拼接到URL後面或參數轉換爲body二進制的細節,這些都由AFNetworking內部處理並進行容錯,開發者只須要指定請求方式便可。

經過AFNetworking實現https也很方便,AFSecurityPolicy能夠很好的管理CA以及自簽名證書,以及處理https請求過程當中的一些邏輯,咱們只須要告訴AFNetworking怎麼處理便可,若是不指定處理方式則使用默認CA證書的方式處理。

AFNetworking對於後臺下載以及斷點續傳有很好的支持,咱們能夠在AFNetworking的基礎上,很簡單的就完成一個下載模塊的設計。若是本身寫後臺下載和斷點續傳的代碼,工做量仍是不小的。

而且AFNetworking在網絡庫的設計上還提供了很強的自定義性,例如指定證書、URL緩存處理,以及下載過程當中不一樣下載階段的處理。若是沒有提供自定義處理,則使用默認處理方式。

相關文章
相關標籤/搜索