AFNetworking多線程分析

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,它提供如下額外的功能:

  1. 設(shè)置任務(wù)隊(duì)列中任務(wù)的依賴(lài)關(guān)系,比如任務(wù)A在任務(wù)B執(zhí)行后執(zhí)行
  2. 可以cancel或者suspend一個(gè)任務(wù)
  3. 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)如下幾步:

  1. 繼承系統(tǒng)的NSOperation
  2. 覆蓋main方法
  3. 在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)求的基本框架如下:

afnetwork
afnetwork

從上圖可以看到,系統(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,AFURLConnectionOperationNSOperation的子類(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)參考如下博客:

iOS中的Run Loop機(jī)制

在執(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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 簡(jiǎn)述 在iOS開(kāi)發(fā)中,與直接使用蘋(píng)果框架中提供的NSURLConnection或NSURLSession進(jìn)行網(wǎng)絡(luò)請(qǐng)...
    zongmumask閱讀 9,482評(píng)論 9 49
  • Object C中創(chuàng)建線程的方法是什么?如果在主線程中執(zhí)行代碼,方法是什么?如果想延時(shí)執(zhí)行代碼、方法又是什么? 1...
    AlanGe閱讀 1,908評(píng)論 0 17
  • *面試心聲:其實(shí)這些題本人都沒(méi)怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來(lái)就是把...
    Dove_iOS閱讀 27,581評(píng)論 30 472
  • 寫(xiě)在開(kāi)頭: 大概回憶下,之前我們講了AFNetworking整個(gè)網(wǎng)絡(luò)請(qǐng)求的流程,包括request的拼接,sess...
    涂耀輝閱讀 20,098評(píng)論 53 315
  • 這周工作忙忙碌碌,準(zhǔn)備便當(dāng)成了一天中少有的空閑時(shí)光,做完飯整個(gè)人就靜下來(lái)了。下面是四天的便當(dāng),又有不少自己發(fā)明的黑...
    愛(ài)吃愛(ài)花愛(ài)生活的UX人閱讀 835評(píng)論 3 3

友情鏈接更多精彩內(nèi)容