AFNetworking多線程分析
AFNetworking是目前最常用的iOS的網(wǎng)絡(luò)開(kāi)發(fā)框架。它是對(duì)Apple系統(tǒng)提供的網(wǎng)絡(luò)框架(NSURLConnection和NSURLSession)的上層封裝。本文針對(duì)AFNetworking的2x版本的NSURLConnection的網(wǎng)絡(luò)請(qǐng)求的工作流程做一個(gè)簡(jiǎn)單的梳理。
NSOperation介紹
在介紹AFNetworking的多線程流程前需要先了解一下NSOperation的基本知識(shí)。
提到并發(fā)相關(guān)的概念,可能會(huì)想到一些關(guān)鍵字,比如線程,鎖等等。iOS開(kāi)發(fā)中也有對(duì)應(yīng)的支持,比如 NSThread,NSLock 等。但是直接使用這些組件來(lái)做并發(fā)會(huì)很復(fù)雜,也極易引入并發(fā)相關(guān)的bug并且難以調(diào)試,尤其是死鎖相關(guān)的問(wèn)題。因此Apple自己封裝了這些低層次的組件,提供了更加易于的并發(fā)模型,即GCD和NSOperation。
GCD和NSOperation都是基于任務(wù)隊(duì)列的并發(fā)模型。使用者不必要關(guān)系線程和鎖,只需要將要執(zhí)行的代碼block放入到任務(wù)隊(duì)列即可。GCD是一個(gè)輕量級(jí)的框架,提供的功能相對(duì)簡(jiǎn)單但是高效。NSOperation是在GCD基礎(chǔ)之上封裝的一個(gè)功能更加強(qiáng)大的框架。提供ObjectC風(fēng)格的API,相比于GCD,它提供如下額外的功能:
- 設(shè)置任務(wù)隊(duì)列中任務(wù)的依賴(lài)關(guān)系,比如任務(wù)A在任務(wù)B執(zhí)行后執(zhí)行
- 可以cancel或者suspend一個(gè)任務(wù)
- NSOperation支持KVO
相比于GCD,NSOperation在性能開(kāi)銷(xiāo)上有一些增加。但是如果要用到一些高級(jí)的功能,這些開(kāi)銷(xiāo)也是值得的。
例如,我們需要?jiǎng)?chuàng)建一個(gè)任務(wù)來(lái)輸出‘Hello world’,只需要實(shí)現(xiàn)如下幾步:
- 繼承系統(tǒng)的NSOperation
- 覆蓋main方法
- 在main方法中實(shí)現(xiàn)代碼邏輯
代碼如下:
#import <Foundation/Foundation.h>
@interface MyOperation: NSOperation
@end
@implementation MyOperation
- (void)main {
@autoreleasepool {
NSLog(@"Hello world");
}
}
@end
具體的API可以參考Concurrency Programming Guide
NSURLConnection
NSURLConnection是Apple提供的網(wǎng)絡(luò)框架。調(diào)用者不需要關(guān)心底層的socket實(shí)現(xiàn),只需要調(diào)用上層API即可。
NSURLConnection提供兩種類(lèi)型的網(wǎng)絡(luò)請(qǐng)求API,一種是同步API,一種是異步API。簽名如下:
// 同步請(qǐng)求
sendAsynchronousRequest:queue:completionHandler:
// 異步請(qǐng)求
sendSynchronousRequest:returningResponse:error:
@end
同步請(qǐng)求會(huì)掛起當(dāng)前線程,而異步請(qǐng)求會(huì)在調(diào)用完成后離開(kāi)返回,當(dāng)網(wǎng)絡(luò)請(qǐng)求完成后會(huì)執(zhí)行相應(yīng)的回調(diào)。AFNetworking中使用的是異步請(qǐng)求的API來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求。
系統(tǒng)框架
AFNetworking中網(wǎng)絡(luò)請(qǐng)求的基本框架如下:

從上圖可以看到,系統(tǒng)維護(hù)了一個(gè)NSOperationQueue。每一次請(qǐng)求都會(huì)構(gòu)建一個(gè)對(duì)應(yīng)的NSOperation,然后加入到該queue中。當(dāng)該operation開(kāi)始執(zhí)行時(shí)(調(diào)用start方法),它在一個(gè)名為AFNetworking的線程中創(chuàng)建了一個(gè)NSURLConnection的實(shí)例,開(kāi)始進(jìn)行實(shí)際的網(wǎng)絡(luò)請(qǐng)求。
流程分析
外層API
下面是一個(gè)使用AFNetworking的常用例子:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:@"http://example.com/resources.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
其中 manager 的GET方法實(shí)現(xiàn)如下:
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:@"GET" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:nil];
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
這段代碼創(chuàng)建了一個(gè)AFHTTPRequestOperation的實(shí)例,然后放到了self.operationQueue中。其中AFHTTPRequestOperationManager就繼承自AFURLConnectionOperation,AFURLConnectionOperation是NSOperation的子類(lèi),它實(shí)現(xiàn)了NSURLConnection的大部分邏輯,包括Request的發(fā)送和Response的處理以及所有的NSURLConnection的Delegate。
窺探AFURLConnectionOperation
在AFHTTPRequestOperation加入到了operationQueue后系統(tǒng)內(nèi)部到底發(fā)生了什么呢?這個(gè)時(shí)候,其實(shí)執(zhí)行了NSOperation的start方法:
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
上述代碼在首先檢查了內(nèi)部的狀態(tài),如果處于Ready狀態(tài),則在某個(gè)線程中執(zhí)行方法operationDidStart。該線程的創(chuàng)建代碼如下:
+ (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;
}
這是個(gè)單例方法,創(chuàng)建了一個(gè)全局唯一的線程,線程名字為AFNetworking。該線程用于實(shí)際的網(wǎng)絡(luò)請(qǐng)求的處理。而方法operationDidStart的內(nèi)容如下:
- (void)operationDidStart {
[self.lock lock];
if (![self isCancelled]) {
// 創(chuàng)建NSURLConnection的實(shí)例
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.connection start];
}
[self.lock unlock];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];
});
}
可以看出,在上述方法中系統(tǒng)實(shí)際的創(chuàng)建了NSURLConnection的實(shí)例,然后執(zhí)行其start方法,開(kāi)始實(shí)際的網(wǎng)絡(luò)請(qǐng)求。
注意這里創(chuàng)建NSURLConnection的代碼和我們平常使用的不同。它首先初始化一個(gè)非立即執(zhí)行的實(shí)例,然后設(shè)置其運(yùn)行的runLoop,最后才調(diào)用start方法開(kāi)始執(zhí)行。每一個(gè)NSThread都有一個(gè)NSRunLoop,可以通過(guò)方法currentRunLoop來(lái)獲取。默認(rèn)情況下NSURLConnection會(huì)在RunloopMode為NSDefaultRunLoopMode時(shí)執(zhí)行。這樣當(dāng)用戶(hù)滑動(dòng)ScrollView等操作時(shí)RunLoopMode為NSEventTrackingRunLoopMode,這樣NSURLConnection相關(guān)的回調(diào)不會(huì)立即執(zhí)行。因此上述代碼手動(dòng)的設(shè)置了RunLoopMode。
如果需要了解RunLoop的詳細(xì)知識(shí),請(qǐng)參考如下博客:
在執(zhí)行完上述代碼后,AFNetworking就交給Apple的NSURLFoundation來(lái)處理實(shí)際的請(qǐng)求。NSURLFoundation每個(gè)版本的實(shí)現(xiàn)可能不一樣,內(nèi)部也會(huì)創(chuàng)建自己的線程。不過(guò)對(duì)于使用者而言,只需要關(guān)心發(fā)送請(qǐng)求和處理響應(yīng)的回調(diào)即可。接下來(lái)發(fā)生的就是NSURLConnection的一系列Delegate的回調(diào)。
Completion Block的蹤跡
在創(chuàng)建AFHTTPRequestOperation時(shí)會(huì)設(shè)置success和failure的回調(diào)。這些回調(diào)會(huì)在網(wǎng)絡(luò)請(qǐng)求完成后觸發(fā),使用者大多只需要關(guān)心這些回調(diào)就行。那些這些回調(diào)又是在什么時(shí)候執(zhí)行的呢?
構(gòu)建AFURLRequestOperation時(shí)創(chuàng)建的回調(diào)block通過(guò)如下方法傳入:
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
self.completionBlock = ^{
// 執(zhí)行success和failure block
}
}
上述方法是設(shè)置了self.completionBlock,該block中會(huì)根據(jù)執(zhí)行的網(wǎng)絡(luò)請(qǐng)求的結(jié)果來(lái)執(zhí)行傳入的success或者failure的回調(diào)。設(shè)置self.completionBlock會(huì)調(diào)用如下方法:
- (void)setCompletionBlock:(void (^)(void))block {
[self.lock lock];
if (!block) {
[super setCompletionBlock:nil];
} else {
__weak __typeof(self)weakSelf = self;
// 調(diào)用NSOperation的setCompletionBlock
[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];
});
}];
}
[self.lock unlock];
}
上述的代碼其實(shí)是調(diào)用了NSOperation的setCompletionBlcock方法。傳入的block會(huì)在什么時(shí)候執(zhí)行呢?其實(shí)會(huì)在NSOperation執(zhí)行完finish之后。調(diào)用finish方法的代碼如下:
- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {
self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
[self.outputStream close];
if (self.responseData) {
self.outputStream = nil;
}
self.connection = nil;
// 調(diào)用NSOperation的finish方法
[self finish];
}
它是在NSURLConnection的Delegate方法中調(diào)用。當(dāng)網(wǎng)絡(luò)請(qǐng)求完成后,connectionDidFinishLoading的Delegate就會(huì)執(zhí)行,該方法中會(huì)調(diào)用NSOperation的finish方法,從而開(kāi)始執(zhí)行self.completionBlock。該block中就有外層傳入的success和failure的回調(diào)。代碼如下
dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
該block會(huì)優(yōu)先放到self.completionQueue,如果該值為空,則會(huì)放到main queue中。這也解釋了Stack Overflow中的如下問(wèn)題:
Are AFNetworking success/failure blocks invoked on the main thread?
默認(rèn)情況下,self.completionQueue是空,因此success和failure的回調(diào)是在主線程中執(zhí)行。所有盡量不要在這些回調(diào)中執(zhí)行過(guò)多占用CPU的代碼。如果這些回調(diào)確實(shí)需要占用CPU,則建議創(chuàng)建一個(gè)單獨(dú)的任務(wù)隊(duì)列,并賦值給afnetworking的completionQueue屬性。
總結(jié)
- 模型分析
AFNetworking并沒(méi)有為每一個(gè)請(qǐng)求創(chuàng)建一個(gè)線程,而是將每個(gè)請(qǐng)求封裝成一個(gè)NSOperation放到一個(gè)queue中。但是每當(dāng)該operation執(zhí)行時(shí),它都會(huì)在一個(gè)單獨(dú)的線程(AFNetworking)中創(chuàng)建NSURLConnection對(duì)象,并監(jiān)聽(tīng)所有的回調(diào)。由于網(wǎng)絡(luò)請(qǐng)求都是采用NSURLConnection或者NSURLSession的異步API,因此一個(gè)單一的處理線程已經(jīng)可以滿(mǎn)足需要。
總而言之,AFNetworking其實(shí)是采用了NSOperationQueue+NSURLFoundation的異步API來(lái)完成高效的網(wǎng)絡(luò)請(qǐng)求。在具體的實(shí)現(xiàn)細(xì)節(jié)上,有很多地方值得學(xué)習(xí)和借鑒。
- 并發(fā)粒度
AFNetworking所有的網(wǎng)絡(luò)請(qǐng)求都是放到了NSOperationQueue中,而該queue會(huì)有多個(gè)并發(fā)的線程來(lái)執(zhí)行。默認(rèn)情況下系統(tǒng)會(huì)根據(jù)硬件的條件,比如CPU的核心數(shù)等來(lái)設(shè)置并發(fā)的線程數(shù),我們也可以手動(dòng)的設(shè)置該變量。
[[self.requestOperationManager operationQueue] setMaxConcurrentOperationCount:2];
上述代碼手動(dòng)的設(shè)置了并發(fā)的線程數(shù)為2。
因此,如果我們需要所有的網(wǎng)絡(luò)請(qǐng)求按照創(chuàng)建的順序序列的執(zhí)行則可以設(shè)置setMaxConcurrentOperationCount為1。
- completion block優(yōu)化
如果某個(gè)operation的success和failure的回調(diào)占用較多的CPU,那么可以創(chuàng)建一個(gè)任務(wù)隊(duì)列并賦值給該operation的completionQueue。