探秘AFNetworking

該文章屬于劉小壯原創(chuàng),轉(zhuǎn)載請(qǐng)注明:劉小壯


AFNetworkingiOS最常用的網(wǎng)絡(luò)框架观挎,雖然系統(tǒng)也有NSURLSession午衰,但是我們一般不會(huì)直接用它括改。AFNetworking經(jīng)過了三個(gè)大版本穴翩,現(xiàn)在用的大多數(shù)都是3.x的版本。

AFNetworking經(jīng)歷了下面三個(gè)階段的發(fā)展:

  • 1.0版本 : 基于NSURLConnection的封裝诀姚。
  • 2.0版本 : 兩套實(shí)現(xiàn),分別基于NSURLConnectionNSURLSession玷禽,是轉(zhuǎn)向NSURLSession的過渡版赫段。
  • 3.0版本 : 基于NSURLSession的封裝。

文件構(gòu)成

文件構(gòu)成

AFNetworking3.X的構(gòu)成很簡(jiǎn)單矢赁,主要就四部分糯笙,除此之外還有一些基于UIKitCategory,但這些并不是標(biāo)配撩银。

  • Manager : 負(fù)責(zé)處理網(wǎng)絡(luò)請(qǐng)求的兩個(gè)Manager给涕,主要實(shí)現(xiàn)都在AFURLSessionManager中。
  • Reachability : 網(wǎng)絡(luò)狀態(tài)監(jiān)控。
  • Security : 處理網(wǎng)絡(luò)安全和HTTPS相關(guān)的够庙。
  • Serialization : 請(qǐng)求和返回?cái)?shù)據(jù)的格式化器恭应。

AFURLSessionManager


AFN3.0中,網(wǎng)絡(luò)請(qǐng)求的manager主要有AFHTTPSessionManagerAFURLSessionManager構(gòu)成耘眨,二者為父子關(guān)系昼榛。這兩個(gè)類職責(zé)劃分很清晰,父類負(fù)責(zé)處理一些基礎(chǔ)的網(wǎng)絡(luò)請(qǐng)求代碼毅桃,并且接受NSURLRequest對(duì)象褒纲,而子類則負(fù)責(zé)處理和http協(xié)議有關(guān)的邏輯。

AFN的這套設(shè)計(jì)很便于擴(kuò)展钥飞,如果以后想增加FTP協(xié)議的處理莺掠,則基于AFURLSessionManager創(chuàng)建子類即可。子類中只需要進(jìn)行很少的代碼處理读宙,創(chuàng)建一個(gè)NSURLRequest對(duì)象后調(diào)用父類代碼彻秆,由父類去完成具體的請(qǐng)求操作。

創(chuàng)建sessionManager

AFHTTPSessionManager類的初始化方法中并沒有太多實(shí)現(xiàn)代碼结闸,其內(nèi)部調(diào)用的都是父類AFURLSessionManagerinitWithSessionConfiguration方法唇兑,下面是此方法內(nèi)部的一些關(guān)鍵代碼。

在初始化方法中包含一個(gè)參數(shù)sessionConfiguration桦锄,如果沒有傳入的話默認(rèn)是使用系統(tǒng)的defaultConfiguration扎附,我們創(chuàng)建是一般都不會(huì)自定義configuration,所以大多數(shù)都是系統(tǒng)的结耀。

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

隨后是NSURLSession的初始化代碼留夜,關(guān)于NSOperationQueue后面詳細(xì)進(jìn)行講解。NSURLSession的初始化方式有兩種图甜,一種是使用系統(tǒng)的共享session碍粥,另一種是自己創(chuàng)建sessionAFN選擇的是創(chuàng)建自己的session黑毅,并且每個(gè)請(qǐng)求都會(huì)創(chuàng)建一個(gè)獨(dú)立的session嚼摩。

可以通過NSURLSession進(jìn)行連接復(fù)用,這樣可以避免很多握手和揮手的過程矿瘦,提高網(wǎng)絡(luò)請(qǐng)求速度枕面,蘋果允許iOS設(shè)備上一個(gè)域名可以有四個(gè)連接同時(shí)存在。但是由于AFN的實(shí)現(xiàn)是每個(gè)請(qǐng)求都創(chuàng)建一個(gè)session匪凡,所以就不能進(jìn)行連接復(fù)用膊畴。

所以可以通過在外面對(duì)AFN進(jìn)行二次封裝,將AFHTTPSessionManager復(fù)用為單例對(duì)象病游,通過復(fù)用sessionManager的方式唇跨,來進(jìn)行連接的復(fù)用稠通。但是這種方案對(duì)于不同的requestSerializerresponseSerializer等情況买猖,還是要做特殊兼容改橘,所以最好建立一個(gè)sessionManager池,對(duì)于同類型的sessionManager直接拿出來復(fù)用玉控,否則就創(chuàng)建新的飞主。

// 共享session連接池
[NSURLSession sharedSession];
// 創(chuàng)建新session,則不能使用共享session連接池
[NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

由于當(dāng)前AFURLSessionManager對(duì)象的所有sessionTask請(qǐng)求任務(wù)高诺,都是共享同一個(gè)回調(diào)代理的碌识,所以AFN為了區(qū)分每個(gè)sessionTask,通過下面的可變字典虱而,將所有taskDelegatetask.taskIdentifier的進(jìn)行了一一對(duì)應(yīng)筏餐,以便于很容易的對(duì)每個(gè)請(qǐng)求task進(jìn)行操作。

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

在初始化方法中牡拇,可以發(fā)現(xiàn)AFN在創(chuàng)建session后魁瞪,調(diào)用了getTasksWithCompletionHandler方法來獲取當(dāng)前所有的task。但是現(xiàn)在剛創(chuàng)建session惠呼,理論上來說是不應(yīng)該有task的导俘。但從AFNissues中找到了答案issues 3499

這是因?yàn)樘尢#?code>completionHandler回調(diào)中旅薄,為了防止進(jìn)入前臺(tái)時(shí),通過session id恢復(fù)的task導(dǎo)致一些崩潰問題泣崩,所以這里將之前的task進(jìn)行遍歷赋秀,并將回調(diào)都置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];
    }
}];

創(chuàng)建task

AFURLSessionManager中進(jìn)行task的創(chuàng)建律想,task的類型總共分為三種,dataTask绍弟、uploadTask技即、downloadTaskAFN并沒有對(duì)streamTask進(jìn)行處理樟遣。

AFHTTPSessionManager在創(chuàng)建GET而叼、POST等請(qǐng)求時(shí),本質(zhì)上都是調(diào)用了下面的方法或其類似的方法豹悬,方法內(nèi)部會(huì)創(chuàng)建一個(gè)task對(duì)象葵陵,并調(diào)用addDelegateForDataTask將后面的處理交給AFURLSessionManagerTaskDelegate來完成。隨后會(huì)將task返回給調(diào)用方瞻佛,調(diào)用方獲取到task對(duì)象后脱篙,也就是子類AFHTTPSessionManager娇钱,會(huì)調(diào)用resume方法開始請(qǐng)求。

- (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;
}

除了普通請(qǐng)求外绊困,upload文搂、download都有類似的處理。

相關(guān)API

addDelegateForDataTask方法中秤朗,會(huì)調(diào)用sessionManagersetDelegate:forTask:方法煤蹭,此方法內(nèi)部將tasktaskDelegate進(jìn)行了注冊(cè)。由于AFN可以通過通知讓外界監(jiān)聽請(qǐng)求狀態(tài)取视,所以在此方法中還監(jiān)聽了taskresumesuspend事件硝皂,并在實(shí)現(xiàn)代碼中將事件廣播出去。

- (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的創(chuàng)建任務(wù)開始作谭,按代碼邏輯跟到這里稽物,發(fā)現(xiàn)其實(shí)AFN3.0的請(qǐng)求代碼真的很簡(jiǎn)單,主要都集中在創(chuàng)建NSMutableURLRequest那里丢早,其他都依賴于NSURLSession姨裸,因?yàn)榇_實(shí)NSURLSessionAPI封裝程度比較好,也很好使用怨酝。

AFN3.0的作用就是對(duì)NSURLSession的封裝性比較好傀缩,你不用去寫太多重復(fù)性的代碼,并且可以很容易的通過block得到回調(diào)結(jié)果农猬。

AFURLSessionManagerTaskDelegate

NSURLSession的回調(diào)方法比較多赡艰,這里只針對(duì)一些關(guān)鍵代碼進(jìn)行講解,以及梳理整體回調(diào)邏輯斤葱,不一一列舉每個(gè)回調(diào)方法的作用慷垮,詳細(xì)源碼各位可以直接下載AFN代碼查看。

AFURLSessionManager中揍堕,有一個(gè)AFURLSessionManagerTaskDelegate類比較重要料身,這個(gè)類和sessionTask是一一對(duì)應(yīng)的,負(fù)責(zé)處理sessionTask請(qǐng)求的很多邏輯衩茸,NSURLSessionDelegate的回調(diào)基本都轉(zhuǎn)發(fā)給taskDelegate去處理了芹血。在NSURLSession回調(diào)中處理了HTTPS證書驗(yàn)證、下載進(jìn)度之類的楞慈,沒有太復(fù)雜的處理幔烛。

taskDelegate的設(shè)計(jì)很不錯(cuò),可以將代理回調(diào)任務(wù)處理對(duì)象化囊蓝,也可以給AFURLSessionManager類瘦身饿悬。比較理想的是直接將代理設(shè)置為taskDelegate,但是由于會(huì)涉及一些AFURLSessionManager自身的處理邏輯聚霜,所以才設(shè)計(jì)為消息傳遞的方式狡恬。

taskDelegate的功能很簡(jiǎn)單珠叔,主要是NSData數(shù)據(jù)的處理,NSProgress上傳下載進(jìn)度的處理傲宜,以及通知參數(shù)的處理运杭。在進(jìn)行AFN的下載處理時(shí),NSData的數(shù)據(jù)拼接函卒、事件回調(diào)辆憔,及文件處理,都是由taskDelegate來完成的报嵌。

下面是downloadTask任務(wù)完成時(shí)的處理代碼虱咧,其他回調(diào)代碼就不一一列舉了。

- (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中有一個(gè)很好的設(shè)計(jì)锚国,taskDelegate并不直接在NSURLSession的代理方法中做進(jìn)度拼接和回調(diào)腕巡。而是對(duì)于上傳和下載任務(wù)分別對(duì)應(yīng)不同的NSProgress,并通過KVO來監(jiān)聽fractionCompleted屬性血筑,并且實(shí)現(xiàn)cancel绘沉、suspend等狀態(tài)回調(diào)。任務(wù)的狀態(tài)和進(jìn)度處理交給NSProgress豺总,在回調(diào)方法中直接拼接NSProgress的進(jìn)度车伞,從而回調(diào)KVO方法。

NSProgress內(nèi)部的cancel喻喳、pause另玖、resume方法,正好可以對(duì)應(yīng)到sessionTask的方法調(diào)用表伦。但是從代碼角度來看谦去,AFN好像并沒有進(jìn)行相關(guān)的調(diào)用,但這個(gè)設(shè)計(jì)思路很好蹦哼。

_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

看過源碼的話鳄哭,可以發(fā)現(xiàn)AFURLSessionManager中還有一個(gè)_AFURLSessionTaskSwizzling類,這里我們簡(jiǎn)稱taskSwizzling類纲熏。我認(rèn)為此類的設(shè)計(jì)實(shí)在是冗余窃诉,此類的主要功能就是在+load方法中進(jìn)行一個(gè)swizzling,將dataTaskresumesuspend方法進(jìn)行替換赤套,并且在替換后的方法中發(fā)出對(duì)應(yīng)的通知,并沒有太多實(shí)際的功能珊膜。

只不過taskSwizzling類中還是有一些不錯(cuò)的代碼設(shè)計(jì)值得借鑒的容握,由于sessionTask存在一系列繼承鏈,所以直接對(duì)其進(jìn)行swizzling對(duì)其他子類并不生效车柠,因?yàn)槊總€(gè)子類都有自己的實(shí)現(xiàn)剔氏,而寫一大堆swizzling又沒有什么技術(shù)含量塑猖。

iOS7iOS8上,sessionTask的繼承關(guān)系并不一樣谈跛,最好進(jìn)行一個(gè)統(tǒng)一的處理羊苟。AFN采取的方式是創(chuàng)建一個(gè)dataTask對(duì)象,并對(duì)這個(gè)對(duì)象進(jìn)行swizzling感憾,并且遍歷其繼承鏈一直進(jìn)行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預(yù)編譯指令

AFN為了避免發(fā)生編譯器警告阻桅,采取了預(yù)編譯指令對(duì)代碼進(jìn)行修飾凉倚,預(yù)編譯指令基本由三部分組成,push嫂沉、pop稽寒、ignored類型。Github上有人維護(hù)了一份clang warning清單趟章,如果想進(jìn)行對(duì)應(yīng)的預(yù)編譯處理可以上去找找有沒有合適的杏糙。

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

線程問題

NSURLSessioniOS8以下會(huì)并發(fā)創(chuàng)建多個(gè)task,但并發(fā)設(shè)置task identifier的時(shí)候會(huì)存在identifier重復(fù)的問題蚓土。為了解決這個(gè)問題宏侍,在iOS8以下,系統(tǒng)將所有sessionTask的創(chuàng)建都放在一個(gè)同步的串行隊(duì)列中進(jìn)行北戏,保證創(chuàng)建及賦值操作是串行進(jìn)行的负芋。

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任務(wù)放在一個(gè)同步隊(duì)列中執(zhí)行嗜愈。這個(gè)問題是由于在iOS8以下并發(fā)創(chuàng)建任務(wù)旧蛾,可能會(huì)有多個(gè)相同的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();
    }
}

一個(gè)比較有意思的是,AFN為了讓開發(fā)者明白為什么要加這個(gè)判斷蠕嫁,對(duì)iOS8系統(tǒng)的判斷定義成了一個(gè)宏锨天,并且用Apple Supportid作為宏定義命名,很見名知意剃毒。

#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0

AFN在回調(diào)didCompleteWithError方法病袄,并處理返回?cái)?shù)據(jù)時(shí),會(huì)切換到其他線程和group去處理赘阀,處理完成后再切換到主線程并通知調(diào)用方益缠。

AFN提供了兩個(gè)屬性,用來設(shè)置請(qǐng)求結(jié)束后進(jìn)行回調(diào)的dispatch queuedispatch group基公,如果不設(shè)置的話幅慌,AFN會(huì)有默認(rèn)的實(shí)現(xiàn)來處理請(qǐng)求結(jié)束的操作。下面是groupqueue的實(shí)現(xiàn)轰豆,AFN對(duì)于返回?cái)?shù)據(jù)的處理胰伍,采用的是并發(fā)處理齿诞。

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在創(chuàng)建AFURLSessionManageroperationQueue時(shí),將其最大并發(fā)數(shù)設(shè)置為1骂租。這是因?yàn)樵趧?chuàng)建NSURLSSession時(shí)祷杈,蘋果要求網(wǎng)絡(luò)請(qǐng)求回來的數(shù)據(jù)順序執(zhí)行,為了保證代理方法的執(zhí)行順序渗饮,所以需要串行的調(diào)用NSURLSSession的代理方法但汞。

AFHTTPSessionManager


AFHTTPSessionManager本質(zhì)上是對(duì)父類AFURLSessionManager的封裝,主要實(shí)現(xiàn)都在父類中抽米,自己內(nèi)部代碼實(shí)現(xiàn)很簡(jiǎn)單特占。在創(chuàng)建AFHTTPSessionManager時(shí)會(huì)傳入一個(gè)baseURL,以及指定requestSerializer云茸、responseSerializer對(duì)象是目。

從代碼實(shí)現(xiàn)來看,AFN的請(qǐng)求并不是單例形式的标捺,每個(gè)請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的請(qǐng)求對(duì)象懊纳。平時(shí)調(diào)用的GETPOST等網(wǎng)絡(luò)請(qǐng)求方法亡容,都定義在AFHTTPSessionManager中嗤疯。AFHTTPSessionManager內(nèi)部則調(diào)用父類方法,發(fā)起響應(yīng)的請(qǐng)求并獲取到task對(duì)象闺兢,調(diào)用taskresume后返回給調(diào)用方茂缚。

- (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負(fù)責(zé)創(chuàng)建NSMutableURLRequest請(qǐng)求對(duì)象,并對(duì)request進(jìn)行請(qǐng)求的參數(shù)拼接屋谭、設(shè)置緩存策略脚囊、設(shè)置請(qǐng)求頭等關(guān)于請(qǐng)求相關(guān)的配置。

AFURLRequestSerialization并不是一個(gè)類桐磁,而是一個(gè)文件悔耘,其中包含三個(gè)requestSerializer請(qǐng)求對(duì)象,分別對(duì)應(yīng)著不同的請(qǐng)求序列化器我擂。

  • AFHTTPRequestSerializer:普通請(qǐng)求衬以。
  • AFJSONRequestSerializer:JSON請(qǐng)求。
  • AFPropertyListRequestSerializer:一種特殊的xml格式請(qǐng)求校摩。

這三個(gè)類區(qū)別就在于Content-Type不同看峻,其他基本都是一樣的。AFN默認(rèn)是HTTP的。

AFURLRequestSerialization協(xié)議

在文件中定義了同名的AFURLRequestSerialization協(xié)議,不同的requestSerializer會(huì)對(duì)協(xié)議方法有不同的實(shí)現(xiàn),下面是AFHTTPRequestSerializer的實(shí)現(xiàn)代碼持寄。其核心代碼實(shí)現(xiàn)也比較直觀,就是在創(chuàng)建requestSerializer的時(shí)候玻佩,設(shè)置請(qǐng)求頭的公共參數(shù),以及將請(qǐng)求參數(shù)通過NSJSONSerialization轉(zhuǎn)換為NSData,并將其賦值給request對(duì)象的httpBody珠闰,下面是精簡(jiǎn)后的核心代碼。

- (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;
}

如果想給網(wǎng)絡(luò)請(qǐng)求設(shè)置請(qǐng)求參數(shù)的話瘫辩,需要通過requestSerializer對(duì)外暴露的API添加參數(shù)伏嗜,AFNrequestManager并不直接對(duì)外提供設(shè)置請(qǐng)求頭的代碼。通過requestSerializer可以對(duì)請(qǐng)求頭進(jìn)行添加和刪除伐厌、以及清空的操作承绸。

從創(chuàng)建AFURLRequestSerialization對(duì)象到最后返回NSURLRequest對(duì)象,中間的過程并不復(fù)雜挣轨,主要是設(shè)置請(qǐng)求頭和拼接參數(shù)军熏,邏輯很清晰。

AFQueryStringPair

AFURLRequestSerialization有一個(gè)很重要的功能就是參數(shù)處理卷扮,AFQueryStringPair就是負(fù)責(zé)處理這些參數(shù)的荡澎。pair類中定義了兩個(gè)屬性,分別對(duì)應(yīng)請(qǐng)求參數(shù)的key晤锹、value摩幔。除此之外,還定義了一些非常實(shí)用的C語言函數(shù)鞭铆。

@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函數(shù)負(fù)責(zé)將請(qǐng)求參數(shù)字典或衡,轉(zhuǎn)成拼接在URL后面的參數(shù)字符串,這個(gè)函數(shù)是AFQueryStringPair類中定義的一個(gè)關(guān)鍵函數(shù)车遂。函數(shù)內(nèi)部通過AFQueryStringPairsFromDictionary函數(shù)將參數(shù)字典封断,轉(zhuǎn)為存儲(chǔ)pair對(duì)象的數(shù)組并進(jìn)行遍歷,遍歷后調(diào)用URLEncodedStringValue方法對(duì)參數(shù)進(jìn)行拼接艰额,最后成為字符串參數(shù)澄港。

URLEncodedStringValue方法實(shí)現(xiàn)很簡(jiǎn)單,就是進(jìn)行一個(gè)key柄沮、value的拼接回梧,并且在中間加上“=”。

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

下面是參數(shù)拼接的代碼祖搓,函數(shù)內(nèi)部會(huì)將原有的參數(shù)狱意,轉(zhuǎn)換為AFQueryStringPair對(duì)象的類型,但之前的層級(jí)結(jié)構(gòu)不變拯欧。這句話是什么意思呢详囤,就是說對(duì)原有傳入的對(duì)象進(jìn)行逐層遞歸調(diào)用,并且將最后一層字典的keyvalue參數(shù)藏姐,轉(zhuǎn)成pair類型的對(duì)象隆箩,并且將嵌套有pair對(duì)象的數(shù)組返回給調(diào)用方。

對(duì)象層級(jí)不變羔杨,但字典捌臊、集合都會(huì)被轉(zhuǎn)換為數(shù)組結(jié)構(gòu),也就是之前傳入字典兜材、數(shù)組理澎、字典的嵌套結(jié)構(gòu),返回的時(shí)候就是數(shù)組曙寡、數(shù)組糠爬、pair的結(jié)構(gòu)返回。

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;
}

設(shè)置NSMutableURLRequest

AFHTTPRequestSerializer在創(chuàng)建NSMutableURLRequest時(shí)举庶,需要為request設(shè)置屬性执隧。serializer對(duì)外提供了和request同名的一些屬性,外界直接調(diào)用serializer即可設(shè)置request的屬性灯变。

AFHTTPRequestSerializer內(nèi)部創(chuàng)建request時(shí)殴玛,并不是根據(jù)設(shè)置request的屬性按個(gè)賦值,而是通過一個(gè)屬性數(shù)組AFHTTPRequestSerializerObservedKeyPaths添祸,將serializer需要賦值給request的屬性滚粟,都放在數(shù)組中。

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時(shí)刃泌,遍歷keyPath數(shù)組并通過KVO的方式凡壤,監(jiān)聽serializer的賦值。如果外界對(duì)serializer對(duì)應(yīng)的屬性進(jìn)行賦值耙替,則將其添加到mutableObservedChangedKeyPaths數(shù)組中亚侠。在創(chuàng)建request對(duì)象是,遍歷mutableObservedChangedKeyPaths數(shù)組并將值賦值給request對(duì)象俗扇。

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];
    }
}

表單提交

當(dāng)進(jìn)行POST表單提交時(shí)硝烂,需要用到AFMultipartFormData協(xié)議。調(diào)用POST方法后铜幽,會(huì)回調(diào)一個(gè)遵守此協(xié)議的對(duì)象滞谢,可以通過此對(duì)象進(jìn)行表單提交操作。

[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];

進(jìn)行表單提交時(shí)除抛,可以直接傳入文件狮杨,也可以傳入路徑。表單提交可以同時(shí)提交多個(gè)文件到忽,理論上數(shù)量不受限制橄教。

緩存策略

AFN的緩存策略和NSURLCache的緩存策略一致,并且直接使用系統(tǒng)的枚舉,這對(duì)iOS開發(fā)者是非常友好的护蝶。下面是枚舉定義华烟,忽略掉一些unimplemented的,和一些重定向到已有枚舉的持灰,可用的都在這垦江。

typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy)
{
    NSURLRequestUseProtocolCachePolicy = 0,
    NSURLRequestReloadIgnoringLocalCacheData = 1,
    NSURLRequestReturnCacheDataElseLoad = 2,
    NSURLRequestReturnCacheDataDontLoad = 3,
    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4,
    NSURLRequestReloadRevalidatingCacheData = 5,
};
  • NSURLRequestUseProtocolCachePolicy,使用協(xié)議指定的緩存策略搅方。
  • NSURLRequestReloadIgnoringLocalCacheData,忽略緩存绽族,直接發(fā)起請(qǐng)求姨涡。
  • NSURLRequestReturnCacheDataElseLoad,不驗(yàn)證緩存過期時(shí)間吧慢,如果有則使用緩存數(shù)據(jù)涛漂,如果不存在則請(qǐng)求服務(wù)器。
  • NSURLRequestReturnCacheDataDontLoad检诗,不驗(yàn)證緩存過期時(shí)間匈仗,如果有則使用緩存數(shù)據(jù),如果不存在則請(qǐng)求失敗逢慌。
  • NSURLRequestReloadIgnoringLocalAndRemoteCacheData悠轩,忽略本地緩存,以及代理等中間介質(zhì)的緩存攻泼。
  • NSURLRequestReloadRevalidatingCacheData火架,和數(shù)據(jù)的源服務(wù)器驗(yàn)證數(shù)據(jù)合法性,如果可以用就直接使用緩存數(shù)據(jù)忙菠,否則從服務(wù)器請(qǐng)求數(shù)據(jù)何鸡。

AFURLResponseSerialization


AFURLResponseSerialization負(fù)責(zé)處理response相關(guān)的邏輯,其功能主要是設(shè)置acceptType牛欢、編碼格式和處理服務(wù)器返回?cái)?shù)據(jù)骡男。同樣的,AFURLResponseSerialization也有同名的協(xié)議傍睹,每個(gè)子類都遵循代理方法并實(shí)現(xiàn)不同的返回值處理代碼隔盛。

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

AFURLRequestSerialization一樣,AFURLResponseSerialization由一個(gè)父類和六個(gè)子類構(gòu)成焰望,子類中有一個(gè)是Mac的骚亿,所以這里不做分析,子類的職責(zé)只是對(duì)acceptType做修改以及處理具體的返回?cái)?shù)據(jù)熊赖。

  • AFHTTPResponseSerializer:公共父類来屠,處理返回值類型為NSData二進(jìn)制。
  • AFJSONResponseSerializer:JSON返回?cái)?shù)據(jù),也是默認(rèn)類型俱笛。
  • AFXMLParserResponseSerializer捆姜,處理XML返回?cái)?shù)據(jù),由系統(tǒng)NSXMLParser負(fù)責(zé)處理迎膜。
  • AFPropertyListResponseSerializer:處理特殊XML返回?cái)?shù)據(jù)泥技,也就是plist數(shù)據(jù)。
  • AFImageResponseSerializer:處理圖片返回?cái)?shù)據(jù)磕仅,這個(gè)類型用的也比較多珊豹。
  • AFCompoundResponseSerializer:處理復(fù)雜數(shù)據(jù),返回結(jié)果類型有多種榕订。

容錯(cuò)處理

由于服務(wù)器有時(shí)候會(huì)返回null的情況店茶,系統(tǒng)會(huì)將其轉(zhuǎn)換為NSNull對(duì)象,而對(duì)NSNull對(duì)象發(fā)送不正確的消息劫恒,就會(huì)導(dǎo)致崩潰贩幻。從服務(wù)器接收到返回值后,AFN會(huì)對(duì)返回值進(jìn)行一個(gè)遞歸查找两嘴,找到所有NSNull對(duì)象并將其移除丛楚,防止出現(xiàn)向NSNull對(duì)象發(fā)送消息導(dǎo)致的崩潰。

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的設(shè)計(jì)技巧


bundleForClass

在使用NSBundle對(duì)象時(shí)憔辫,我們最常用的就是mainBundle或者bundleWithPath這種方式獲取bundle趣些,這種對(duì)于都是從app二進(jìn)制讀取的時(shí)候是沒有問題的。但是如果涉及到framework動(dòng)態(tài)庫贰您,就不是那么易于使用喧务。

framework中可以包含資源文件,例如.bundle文件枉圃。如果是動(dòng)態(tài)庫形式的framework(framework也有靜態(tài)形式)功茴,其會(huì)以一個(gè)獨(dú)立二進(jìn)制的形式表現(xiàn),并且會(huì)分配獨(dú)立的二進(jìn)制空間孽亲。在讀取bundle的時(shí)候坎穿,就可以考慮使用bundleForClass的方式讀取。

bundleForClass表示從當(dāng)前類定義的二進(jìn)制返劲,所在的程序包中讀取NSBundle文件玲昧。例如.app就是從main bundle中讀取,如果是framework就從其所在的二進(jìn)制中讀取篮绿。

網(wǎng)絡(luò)指示器

AFN提供了一些UIKitCategory孵延,例如網(wǎng)絡(luò)請(qǐng)求發(fā)起時(shí),網(wǎng)絡(luò)指示器轉(zhuǎn)菊花亲配,則由AFNetworkActivityIndicatorManager類負(fù)責(zé)尘应。開啟網(wǎng)絡(luò)指示器很簡(jiǎn)單惶凝,添加下面代碼即可,網(wǎng)絡(luò)指示器默認(rèn)是關(guān)閉的犬钢。

[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];

這里不對(duì)AFNetworkActivityIndicatorManager的代碼進(jìn)行過多的分析苍鲜,只是調(diào)其中比較重要的點(diǎn)來分析,下面統(tǒng)稱為indicatorManager玷犹。

之前在_AFURLSessionTaskSwizzling類中寫了很多代碼混滔,就是為了發(fā)出resumesuspend兩個(gè)通知,這兩個(gè)通知在indicatorManager中就用到了歹颓。網(wǎng)絡(luò)指示器監(jiān)聽了下面的三個(gè)通知坯屿,并且完全由通知來驅(qū)動(dòng)。

[[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中的源碼巍扛,你會(huì)發(fā)現(xiàn)為什么里面還有timer愿伴,完全不需要啊,有網(wǎng)絡(luò)請(qǐng)求就轉(zhuǎn)菊花电湘,沒網(wǎng)絡(luò)請(qǐng)求就停止不就行了嗎?

這是因?yàn)?code>AFN考慮鹅经,如果一個(gè)網(wǎng)絡(luò)請(qǐng)求很快的話寂呛,會(huì)導(dǎo)致菊花出現(xiàn)轉(zhuǎn)一下很快就消失的情況,如果網(wǎng)絡(luò)請(qǐng)求比較多會(huì)多次閃現(xiàn)瘾晃。所以對(duì)于這個(gè)問題贷痪,indicatorManager通過Timer的方式實(shí)現(xiàn),如果在指定的區(qū)間內(nèi)網(wǎng)絡(luò)請(qǐng)求已經(jīng)結(jié)束蹦误,則不在顯示菊花劫拢,如果有多次請(qǐng)求則在請(qǐng)求之間也不進(jìn)行中斷。

對(duì)于開始轉(zhuǎn)圈設(shè)置的是1.0秒强胰,結(jié)束轉(zhuǎn)圈設(shè)置的是0.17秒舱沧。也就是當(dāng)菊花開始旋轉(zhuǎn)時(shí),需要有1.0秒的延時(shí)偶洋,這個(gè)時(shí)間足以保證之前的菊花停止轉(zhuǎn)動(dòng)熟吏。結(jié)束轉(zhuǎn)圈則會(huì)在0.17秒之后進(jìn)行,可以保證菊花的旋轉(zhuǎn)至少會(huì)有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是采用通知的方式進(jìn)行回調(diào)牵寺,所有的網(wǎng)絡(luò)請(qǐng)求通知都會(huì)調(diào)到這。所以當(dāng)多個(gè)網(wǎng)絡(luò)請(qǐng)求到來時(shí)恩脂,會(huì)通過一個(gè)_activityCount來進(jìn)行計(jì)數(shù)帽氓,可以將其理解為一個(gè)隊(duì)列,這樣更容易理解俩块。在顯示網(wǎng)絡(luò)指示器的時(shí)候黎休,就是基于_activityCount來進(jìn)行判斷的浓领,如果隊(duì)列中有請(qǐng)求則顯示網(wǎng)絡(luò)指示器,無論有多少請(qǐng)求奋渔。

這種設(shè)計(jì)思路比較好镊逝,在項(xiàng)目中很多地方都可以用到。例如有些方法需要成對(duì)進(jìn)行調(diào)用嫉鲸,例如播放開始和暫停撑蒜,如果某一個(gè)方法調(diào)用多次就會(huì)造成bug。這種方式就比較適合用count的方式進(jìn)行容錯(cuò)玄渗,內(nèi)部針對(duì)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是多線程安全的,在一些關(guān)鍵地方都通過synchronized的方式加鎖藤树,防止從各個(gè)線程調(diào)用過來的通知造成資源搶奪的問題浴滴。

AFSecurityPolicy


驗(yàn)證處理

AFN支持https請(qǐng)求,并通過AFSecurityPolicy類來處理https證書及驗(yàn)證岁钓,但其https請(qǐng)求的執(zhí)行還是交給NSURLSession去完成的升略。

下面是NSURLSession的一個(gè)代理方法,當(dāng)需要進(jìn)行證書驗(yàn)證時(shí)屡限,可以重寫此方法并進(jìn)行自定義的驗(yàn)證處理品嚣。驗(yàn)證完成后通過completionHandlerblock來告知處理結(jié)果,并且將驗(yàn)證結(jié)果disposition和公鑰credential傳入钧大。

AFN通過AFSecurityPolicy類提供了驗(yàn)證邏輯翰撑,并且在內(nèi)部可以進(jìn)行證書的管理。也可以不使用AFN提供的驗(yàn)證邏輯啊央,重寫sessionDidReceiveAuthenticationChallengeblock即可自定義驗(yàn)證邏輯眶诈,不走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);
    }
}

除了進(jìn)行NSURLSession請(qǐng)求驗(yàn)證的回調(diào)瓜饥,對(duì)于每個(gè)task也有對(duì)應(yīng)的代理方法逝撬。兩個(gè)代理方法內(nèi)部實(shí)現(xiàn)基本一樣,區(qū)別在于對(duì)于每個(gè)task乓土,AFN提供了taskDidReceiveAuthenticationChallenge回調(diào)block球拦,可以由外界自定義證書驗(yàn)證過程。

驗(yàn)證結(jié)果是通過一個(gè)枚舉回調(diào)給NSURLSession的帐我,參數(shù)是一個(gè)NSURLSessionAuthChallengeDisposition類型的枚舉坎炼,表示證書驗(yàn)證的情況,此枚舉包含下面幾個(gè)具體值拦键。

  • NSURLSessionAuthChallengeUseCredential
    使用當(dāng)前證書建立SSL連接谣光,并處理后續(xù)請(qǐng)求
  • NSURLSessionAuthChallengePerformDefaultHandling
    使用默認(rèn)的處理方式,當(dāng)前證書被忽略
  • NSURLSessionAuthChallengeCancelAuthenticationChallenge
    驗(yàn)證不通過芬为,取消整個(gè)網(wǎng)絡(luò)請(qǐng)求
  • NSURLSessionAuthChallengeRejectProtectionSpace
    這次驗(yàn)證被忽略萄金,但不取消網(wǎng)絡(luò)請(qǐng)求

Security


HTTPS請(qǐng)求的密鑰管理等安全相關(guān)的處理蟀悦,都放在Security.framework框架中。在AFSecurityPolicy中經(jīng)逞醺遥可以看到SecTrustRef類型的變量日戈,其表示的就是密鑰對(duì)象,其中包含了公鑰等信息孙乖。

我們可以通過下面的命令獲取到公鑰浙炼,具體格式這里不做過多介紹,詳細(xì)的可以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的職責(zé)比較單一弯屈,只處理公鑰和驗(yàn)證的邏輯,其定義是一個(gè)單例對(duì)象恋拷。此類主要由四個(gè)屬性和一個(gè)方法構(gòu)成资厉。

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

如果進(jìn)行細(xì)分的話,AFSecurityPolicy的功能基本就兩個(gè)蔬顾。一個(gè)是通過CA的方式進(jìn)行驗(yàn)證宴偿,另一個(gè)就是進(jìn)行SSL Pinning自簽名驗(yàn)證。evaluateServerTrust:forDomain:AFSecurityPolicy最主要的方法诀豁,用來進(jìn)行證書的合法性驗(yàn)證窄刘。

SSL Pinning

AFSecurityPolicy進(jìn)行SSL Pinning驗(yàn)證的方式分為以下三種,如果是None則會(huì)執(zhí)行正常CA驗(yàn)證的流程且叁,其他兩種都是自簽名的流程。AFN中默認(rèn)的調(diào)用是defaultPolicy方法秩伞,其內(nèi)部設(shè)置的是AFSSLPinningModeNone模式逞带。

  • AFSSLPinningModeNone
    正常流程,通過CA機(jī)構(gòu)頒發(fā)的公鑰纱新,對(duì)服務(wù)器下發(fā)的證書驗(yàn)證數(shù)字簽名展氓,并且獲得公鑰。
  • AFSSLPinningModeCertificate
    不通過CA的流程進(jìn)行驗(yàn)證脸爱,而是通過本地內(nèi)置的服務(wù)端證書進(jìn)行驗(yàn)證遇汞,驗(yàn)證過程分為兩步。首先驗(yàn)證證書是否過期或失效簿废,其次驗(yàn)證本地是否包含此證書空入。
  • AFSSLPinningModePublicKey
    不進(jìn)行CA的驗(yàn)證,也不驗(yàn)證證書族檬,只驗(yàn)證公鑰是否有效歪赢。

對(duì)于本地自簽名證書的管理有兩種方式,一種是默認(rèn)會(huì)在本地查找遍歷所有.cer的文件单料,并存在一個(gè)自簽名證書的集合中埋凯。也可以在創(chuàng)建AFSecurityPolicy對(duì)象時(shí)傳入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在進(jìn)行握手時(shí)白对,需要通過CA的公鑰進(jìn)行驗(yàn)證掠廓,保證服務(wù)器公鑰的合法性,沒有被篡改為有問題的公鑰甩恼。如果使用CA機(jī)構(gòu)頒發(fā)的證書蟀瞧,無論使用NSURLSession還是AFNetworking都不需要修改代碼,這些都會(huì)自動(dòng)完成媳拴。如果不想使用CA的證書驗(yàn)證黄橘,例如自簽名證書在CA證書驗(yàn)證時(shí)就會(huì)失敗。

這種情況可以使用自簽名的方式進(jìn)行驗(yàn)證屈溉,也就是在客戶端本地內(nèi)置一份證書塞关,服務(wù)器進(jìn)行四次握手時(shí),通過保存在本地的證書和服務(wù)器進(jìn)行對(duì)比子巾,證書相同則表示驗(yàn)證成功帆赢,不走CA的驗(yàn)證方式。

AFN為我們提供了自簽名證書的驗(yàn)證方法线梗,通過SSLPinningMode設(shè)置驗(yàn)證方式為自簽名椰于,并且傳入證書集合。如果沒有傳入證書集合仪搔,則AFN默認(rèn)會(huì)遍歷整個(gè)沙盒瘾婿,查找所有.cer的證書。

進(jìn)行沙盒驗(yàn)證時(shí)烤咧,需要將AFSecurityPolicyallowInvalidCertificates設(shè)置為YES偏陪,默認(rèn)是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兩部分組成,并且分別對(duì)應(yīng)不同的類昌阿,這里主要介紹NSURLConnection部分的源碼實(shí)現(xiàn)饥脑。

相關(guān)類

總體概覽

NSURLConnection實(shí)現(xiàn)由下面三個(gè)類構(gòu)成,從源碼構(gòu)成可以看出懦冰,無論是session還是connection方案灶轰,都具有很好的擴(kuò)展性。例如這里AFHTTPRequestOperation是基于AFURLConnectionOperation實(shí)現(xiàn)的刷钢,如果需要實(shí)現(xiàn)FTP協(xié)議框往,則可以創(chuàng)建一個(gè)繼承自AFURLConnectionOperationAFFPTConnectionOperation類并重寫對(duì)應(yīng)方法即可。

  • AFURLConnectionOperation
    繼承自NSOperation闯捎,負(fù)責(zé)網(wǎng)絡(luò)請(qǐng)求的邏輯實(shí)現(xiàn)椰弊,每個(gè)網(wǎng)絡(luò)請(qǐng)求就是一個(gè)Operation對(duì)象许溅。
  • AFHTTPRequestOperation
    繼承自AFURLConnectionOperation,處理HTTP相關(guān)網(wǎng)絡(luò)請(qǐng)求秉版。
  • AFHTTPRequestOperationManager
    內(nèi)部持有一個(gè)NSOperationQueue贤重,負(fù)責(zé)管理所有Operation網(wǎng)絡(luò)請(qǐng)求。

AFURLConnectionOperation

下面是AFURLConnectionOperation的初始化方法清焕,和AFURLSessionManager有些不一樣并蝗。其內(nèi)部增加了狀態(tài)的概念,以及RunloopMode的概念秸妥,這兩個(gè)我們后面會(huì)詳細(xì)講解滚停。shouldUseCredentialStorage表示是否由系統(tǒng)做證書驗(yàn)證,后面設(shè)置了securityPolicy粥惧,和sessionManager一樣也是使用默認(rèn)方案键畴。

- (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和網(wǎng)絡(luò)請(qǐng)求的過程很像突雪,有開始起惕、暫停、完成等咏删,并且很好的支持KVO監(jiān)聽惹想,所以AFN將每個(gè)網(wǎng)絡(luò)請(qǐng)求都當(dāng)做一個(gè)Operation任務(wù)。AFURLConnectionOperation可以設(shè)置任務(wù)優(yōu)先級(jí)督函,也可以通過AFHTTPRequestOperationManager設(shè)置最大并發(fā)數(shù)嘀粱,基本上NSOperationQueue提供的功能都能用。

AFHTTPRequestOperationManager中定義了NSOperationQueue辰狡,創(chuàng)建網(wǎng)絡(luò)請(qǐng)求任務(wù)后锋叨,都會(huì)被加入到queue中,隨后由系統(tǒng)調(diào)用queue中的operation任務(wù)搓译,執(zhí)行operationstart方法發(fā)起請(qǐng)求悲柱。AFURLConnectionOperation只需要在內(nèi)部實(shí)現(xiàn)start锋喜、pause些己、resume等父類方法即可,其他都由系統(tǒng)去進(jìn)行調(diào)用嘿般。這種設(shè)計(jì)可以很好的將manageroperation進(jìn)行解耦段标,二者不用直接發(fā)生調(diào)用關(guān)系。

NSURLConnection中炉奴,每個(gè)網(wǎng)絡(luò)請(qǐng)求對(duì)應(yīng)一個(gè)AFHTTPRequestOperation逼庞,所有網(wǎng)絡(luò)請(qǐng)求都共用一個(gè)manager來管理operation。而AFHTTPSessionManager則不同瞻赶,每個(gè)網(wǎng)絡(luò)請(qǐng)求對(duì)應(yīng)一個(gè)manager以及一個(gè)task赛糟。

state設(shè)計(jì)

AFURLConnectionOperation支持KVO的方式派任,讓外界監(jiān)聽網(wǎng)絡(luò)請(qǐng)求的變化,并通過重寫setState方法璧南,在內(nèi)部加入willChangeValueForKey觸發(fā)KVO回調(diào)掌逛。AFN通過AFOperationState來管理網(wǎng)絡(luò)請(qǐng)求狀態(tài),下面是AFN對(duì)其的狀態(tài)定義司倚。

  • AFOperationPausedState
    請(qǐng)求暫停
  • AFOperationReadyState
    請(qǐng)求已準(zhǔn)備好
  • AFOperationExecutingState
    請(qǐng)求正在執(zhí)行中
  • AFOperationFinishedState
    請(qǐng)求完成

當(dāng)網(wǎng)絡(luò)請(qǐng)求狀態(tài)發(fā)生改變時(shí)豆混,都會(huì)調(diào)用setState方法進(jìn)行賦值,例如下面是請(qǐng)求完成時(shí)的處理代碼动知。除此之外皿伺,當(dāng)判斷AFN請(qǐng)求狀態(tài)時(shí),也是通過這個(gè)屬性作為判斷依據(jù)的盒粮。

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

常駐線程

AFURLConnectionOperation中設(shè)計(jì)了常駐線程鸵鸥,并且重寫了operationstart等方法,網(wǎng)絡(luò)請(qǐng)求的start拆讯、cancel脂男、pause等操作,都是在常駐線程中完成的种呐。網(wǎng)絡(luò)請(qǐng)求結(jié)束后嗅榕,數(shù)據(jù)回調(diào)的處理也是在這個(gè)線程中完成的。

這是因?yàn)樵谀膫€(gè)線程創(chuàng)建NSURLConnection對(duì)象并發(fā)出請(qǐng)求斋枢,則數(shù)據(jù)返回時(shí)也默認(rèn)從那個(gè)線程接受數(shù)據(jù)已维。如果請(qǐng)求都是從主線程發(fā)出的,請(qǐng)求返回時(shí)如果屏幕正在滑動(dòng)阔墩,runloopModeUITrackingRunLoopMode則不能處理返回?cái)?shù)據(jù)嘿架。而如果把網(wǎng)絡(luò)請(qǐng)求都加到主線程的NSRunLoopCommonModes中,在大量網(wǎng)絡(luò)請(qǐng)求返回時(shí)啸箫,處理返回?cái)?shù)據(jù)會(huì)影響屏幕滑動(dòng)FPS耸彪。

所以為了保證網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)可以正常返回并被處理,而又不影響屏幕FPS忘苛,則用一個(gè)單獨(dú)的線程來處理蝉娜。如果每個(gè)請(qǐng)求都對(duì)應(yīng)一個(gè)線程來處理返回任務(wù),會(huì)造成大量線程的占用扎唾,所以用一個(gè)常駐線程來處理所有網(wǎng)絡(luò)請(qǐng)求召川,來保證線程資源的最小占用。常駐線程實(shí)際上是一個(gè)單例線程胸遇,并且這個(gè)單例線程被加入了一個(gè)Port進(jìn)行庇牛活,保證線程可以不被退出。

+ (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發(fā)起網(wǎng)絡(luò)請(qǐng)求時(shí)倍阐,實(shí)際創(chuàng)建connection對(duì)象的代碼在下面的方法中概疆。在創(chuàng)建connection對(duì)象后,并沒有立即發(fā)出網(wǎng)絡(luò)請(qǐng)求峰搪,而是將startImmediately設(shè)置為NO届案。隨后會(huì)設(shè)置NSURLConnectionNSOutputStreamRunloopMode,網(wǎng)絡(luò)請(qǐng)求會(huì)從單例線程的runLoopModes中發(fā)出罢艾,這樣當(dāng)網(wǎng)絡(luò)請(qǐng)求返回時(shí)楣颠,回調(diào)代碼也會(huì)在runLoopModes中去執(zhí)行。

operationDidStart方法中會(huì)調(diào)用NSURLConnectionscheduleInRunLoop:forMode:方法咐蚯,將網(wǎng)絡(luò)請(qǐng)求任務(wù)派發(fā)到Runloop指定的Mode中童漩。我覺得給Operation設(shè)置runLoopModes其實(shí)意義不大,因?yàn)槌qv線程基本上只會(huì)有一個(gè)Mode春锋,也就是NSRunloopDefaultMode矫膨,基本上不會(huì)有其他Mode,所以這里設(shè)置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實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求的侧馅,這里簡(jiǎn)單講一下operation中代理方法的實(shí)現(xiàn)。

AFN實(shí)現(xiàn)了https證書驗(yàn)證的代碼呐萌,具體實(shí)現(xiàn)和AFURLSessionManager基本類似馁痴,并且也是通過AFSecurityPolicy來處理具體的證書驗(yàn)證邏輯。

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

關(guān)于請(qǐng)求服務(wù)器數(shù)據(jù)這塊值得講一下肺孤,在NSURLConnection接收服務(wù)器數(shù)據(jù)時(shí)罗晕,AFN通過創(chuàng)建了一個(gè)outputStream,來承載和組織具體的數(shù)據(jù)赠堵,并且在內(nèi)存中進(jìn)行存儲(chǔ)小渊。當(dāng)沒有可用空間或發(fā)生其他錯(cuò)誤時(shí),會(huì)通過streamError的方式進(jìn)行體現(xiàn)茫叭。

當(dāng)網(wǎng)絡(luò)請(qǐng)求結(jié)束時(shí)酬屉,會(huì)調(diào)用didFinishLoading方法,AFN會(huì)從outputStream中拿出數(shù)據(jù)并賦值給responseData揍愁,當(dāng)做返回值數(shù)據(jù)使用呐萨。

- (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,也會(huì)有與之對(duì)應(yīng)的inputStream吗垮,inputStream實(shí)現(xiàn)很簡(jiǎn)單垛吗,就是修改NSMutableURLRequestHTTPBodyStream凹髓。

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

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

內(nèi)存管理


在創(chuàng)建AFHTTPRequestOperation時(shí)會(huì)將successfailureblock傳給operation烁登,并且在operation執(zhí)行完成并回調(diào)completionBlock時(shí),執(zhí)行這兩個(gè)block代碼。但是由于completionBlock中直接使用了self饵沧,導(dǎo)致了循環(huán)引用的問題锨络。

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

completionBlock的循環(huán)引用是AFN有意而為之的,為的就是保持operation的生命周期狼牺,以保證請(qǐng)求處理完成并接收返回的block回調(diào)羡儿。

對(duì)于循環(huán)引用的生命周期,AFN采取的是主動(dòng)打破循環(huán)引用的方式是钥,也就是重寫父類的completionBlock掠归,并且在調(diào)用block結(jié)束后,主動(dòng)將completionBlock賦值為nil悄泥,從而主動(dòng)打破循環(huán)引用虏冻。

- (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弹囚,用來做網(wǎng)絡(luò)狀態(tài)監(jiān)控的厨相。AFNetworkingYYKit鸥鹉、蘋果官方都提供有ReachabilityAPI使用蛮穿,內(nèi)部實(shí)現(xiàn)原理基本差不多。

代碼實(shí)現(xiàn)也很簡(jiǎn)單毁渗,主要依賴SystemConfiguration.framework框架的SCNetworkReachability践磅,注冊(cè)一個(gè)Callback然后等著回調(diào)就可以。這里講一下核心邏輯灸异,一些細(xì)枝末節(jié)的就忽略了音诈。

Reachability提供了兩種初始化方法,一種是通過域名初始化的managerForDomain:方法绎狭,傳入一個(gè)域名细溅,基于這個(gè)域名的訪問情況來判斷當(dāng)前網(wǎng)絡(luò)狀態(tài)。另一種是通過地址初始化的managerForAddress:方法儡嘶,創(chuàng)建一個(gè)sockaddr_in對(duì)象喇聊,并基于這個(gè)對(duì)象來判斷網(wǎng)絡(luò)狀態(tài)。

+ (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中是開啟網(wǎng)絡(luò)監(jiān)測(cè)的核心代碼蹦狂,主要邏輯是設(shè)置了兩個(gè)Callback誓篱,一個(gè)是block的一個(gè)是函數(shù)的,并添加到Runloop中開始監(jiān)控凯楔。由此可以推測(cè)窜骄,Reachability的代碼實(shí)現(xiàn)主要依賴Runloop的事件循環(huán),并且在事件循環(huán)中判斷網(wǎng)絡(luò)狀態(tài)摆屯。

當(dāng)網(wǎng)絡(luò)發(fā)生改變時(shí)邻遏,就會(huì)回調(diào)AFNetworkReachabilityCallback函數(shù),回調(diào)有三個(gè)參數(shù)。targetSCNetworkReachabilityRef對(duì)象准验,flags是網(wǎng)絡(luò)狀態(tài)赎线,info是我們?cè)O(shè)置的block回調(diào)參數(shù)『ィ回調(diào)Callback函數(shù)后垂寥,內(nèi)部會(huì)通過block以及通知的形式,對(duì)外發(fā)出回調(diào)另锋。

- (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總結(jié)


AFNetworking對(duì)請(qǐng)求數(shù)據(jù)的序列化滞项,以及返回?cái)?shù)據(jù)的反序列化做了很多處理。使開發(fā)者只需要傳入一個(gè)字典即可構(gòu)建請(qǐng)求參數(shù)夭坪,無需處理拼接到URL后面或參數(shù)轉(zhuǎn)換為body二進(jìn)制的細(xì)節(jié)蓖扑,這些都由AFNetworking內(nèi)部處理并進(jìn)行容錯(cuò),開發(fā)者只需要指定請(qǐng)求方式即可台舱。

通過AFNetworking實(shí)現(xiàn)https也很方便律杠,AFSecurityPolicy可以很好的管理CA以及自簽名證書,以及處理https請(qǐng)求過程中的一些邏輯竞惋,我們只需要告訴AFNetworking怎么處理即可柜去,如果不指定處理方式則使用默認(rèn)CA證書的方式處理。

AFNetworking對(duì)于后臺(tái)下載以及斷點(diǎn)續(xù)傳有很好的支持拆宛,我們可以在AFNetworking的基礎(chǔ)上嗓奢,很簡(jiǎn)單的就完成一個(gè)下載模塊的設(shè)計(jì)。如果自己寫后臺(tái)下載和斷點(diǎn)續(xù)傳的代碼浑厚,工作量還是不小的股耽。

并且AFNetworking在網(wǎng)絡(luò)庫的設(shè)計(jì)上還提供了很強(qiáng)的自定義性,例如指定證書钳幅、URL緩存處理物蝙,以及下載過程中不同下載階段的處理。如果沒有提供自定義處理敢艰,則使用默認(rèn)處理方式诬乞。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市钠导,隨后出現(xiàn)的幾起案子震嫉,更是在濱河造成了極大的恐慌,老刑警劉巖牡属,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件票堵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡逮栅,警方通過查閱死者的電腦和手機(jī)悴势,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門窗宇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瞳浦,你說我怎么就攤上這事》鲜浚” “怎么了叫潦?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)官硝。 經(jīng)常有香客問我矗蕊,道長(zhǎng),這世上最難降的妖魔是什么氢架? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任傻咖,我火速辦了婚禮,結(jié)果婚禮上岖研,老公的妹妹穿的比我還像新娘卿操。我一直安慰自己,他們只是感情好孙援,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布害淤。 她就那樣靜靜地躺著,像睡著了一般拓售。 火紅的嫁衣襯著肌膚如雪窥摄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天础淤,我揣著相機(jī)與錄音崭放,去河邊找鬼。 笑死鸽凶,一個(gè)胖子當(dāng)著我的面吹牛币砂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播玻侥,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼道伟,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了使碾?” 一聲冷哼從身側(cè)響起蜜徽,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎票摇,沒想到半個(gè)月后拘鞋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡矢门,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年盆色,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灰蛙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡隔躲,死狀恐怖摩梧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宣旱,我是刑警寧澤仅父,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站浑吟,受9級(jí)特大地震影響笙纤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜组力,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一省容、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧燎字,春花似錦腥椒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至脱柱,卻和暖如春伐弹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背榨为。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工惨好, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人随闺。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓日川,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親矩乐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子龄句,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359