AFNetworking到底做了什么?(終)

寫在開頭:
開始正文

首先我們來看看AF2.x的項目目錄:

AF2.X源碼結(jié)構(gòu)圖.png

除了UIKit擴展外,大概就是上述這么多類,其中最重要的有3個類:

1)AFURLConnectionOperation
2)AFHTTPRequestOperation
3)AFHTTPRequestOperationManager

  • 大家都知道,AF2.x是基于NSURLConnection來封裝的,而NSURLConnection的創(chuàng)建以及數(shù)據(jù)請求,就被封裝在AFURLConnectionOperation這個類中。所以這個類基本上是AF2.x最底層也是最核心的類。
  • AFHTTPRequestOperation是繼承自AFURLConnectionOperation,對它父類一些方法做了些封裝。
  • AFHTTPRequestOperationManager則是一個管家,去管理這些這些operation。
我們接下來按照網(wǎng)絡(luò)請求的流程去看看AF2.x的實現(xiàn):

注:本文會涉及一些NSOperationQueue、NSOperation方面的知識,如果對這方面的內(nèi)容不了解的話,可以先看看雷純峰的這篇:
iOS 并發(fā)編程之 Operation Queues

首先,我們來寫一個get或者post請求:
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:url parameters:params
     success:^(AFHTTPRequestOperation *operation, id responseObject) {
         
     } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
         
     }];

就這么簡單的幾行代碼,完成了一個網(wǎng)絡(luò)請求。

接著我們來看看AFHTTPRequestOperationManager的初始化方法:

+ (instancetype)manager {
    return [[self alloc] initWithBaseURL:nil];
}

- (instancetype)init {
    return [self initWithBaseURL:nil];    
}
- (instancetype)initWithBaseURL:(NSURL *)url {
    self = [super init];
    if (!self) {
        return nil;
    }
    // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }
    self.baseURL = url;
    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
    //用來調(diào)度所有請求的queue
    self.operationQueue = [[NSOperationQueue alloc] init];
    //是否做證書驗證
    self.shouldUseCredentialStorage = YES;
    return self;
}

初始化方法很簡單,基本和AF3.x類似,除了一下兩點:
1)設(shè)置了一個operationQueue,這個隊列,用來調(diào)度里面所有的operation,在AF2.x中,每一個operation就是一個網(wǎng)絡(luò)請求。
2)設(shè)置shouldUseCredentialStorage為YES,這個后面會傳給operation,operation會根據(jù)這個值,去返回給代理,系統(tǒng)是否做https的證書驗證。

然后我們來看看get方法:
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
                     parameters:(id)parameters
                        success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                        failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    //拿到request
    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;
}

方法很簡單,如下:
1)用self.requestSerializer生成了一個request,至于如何生成,可以參考之前的文章,這里就不贅述了。
2)生成了一個AFHTTPRequestOperation,然后把這個operation加到我們一開始創(chuàng)建的queue中。

其中創(chuàng)建AFHTTPRequestOperation方法如下:

- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
                                                    success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                                                    failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    //創(chuàng)建自定義的AFHTTPRequestOperation
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    operation.responseSerializer = self.responseSerializer;
    operation.shouldUseCredentialStorage = self.shouldUseCredentialStorage;
    operation.credential = self.credential;
    //設(shè)置自定義的安全策略
    operation.securityPolicy = self.securityPolicy;

    [operation setCompletionBlockWithSuccess:success failure:failure];
    operation.completionQueue = self.completionQueue;
    operation.completionGroup = self.completionGroup;
    return operation;
}

方法創(chuàng)建了一個AFHTTPRequestOperation,并把自己的一些參數(shù)交給了這個operation處理。

接著往里看:
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
    self = [super initWithRequest:urlRequest];
    if (!self) {
        return nil;
    }

    self.responseSerializer = [AFHTTPResponseSerializer serializer];
    return self;
}

除了設(shè)置了一個self.responseSerializer,實際上是調(diào)用了父類,也是我們最核心的類AFURLConnectionOperation的初始化方法,首先我們要明確這個類是繼承自NSOperation的,然后我們接著往下看:

//初始化
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
    NSParameterAssert(urlRequest);

    self = [super init];
    if (!self) {
        return nil;
    }

    //設(shè)置為ready
    _state = AFOperationReadyState;
    //遞歸鎖
    self.lock = [[NSRecursiveLock alloc] init];
    self.lock.name = kAFNetworkingLockName;
    self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
    self.request = urlRequest;
    
    //是否應(yīng)該咨詢證書存儲連接
    self.shouldUseCredentialStorage = YES;

    //https認(rèn)證策略
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

    return self;
}

初始化方法中,初始化了一些屬性,下面我們來簡單的介紹一下這些屬性:

  1. _state設(shè)置為AFOperationReadyState 準(zhǔn)備就緒狀態(tài),這是個枚舉:
typedef NS_ENUM(NSInteger, AFOperationState) {
    AFOperationPausedState      = -1,  //停止
    AFOperationReadyState       = 1,   //準(zhǔn)備就緒
    AFOperationExecutingState   = 2,  //正在進行中
    AFOperationFinishedState    = 3,  //完成
};

這個_state標(biāo)志著這個網(wǎng)絡(luò)請求的狀態(tài),一共如上4種狀態(tài)。這些狀態(tài)其實對應(yīng)著operation如下的狀態(tài):

//映射這個operation的各個狀態(tài)
static inline NSString * AFKeyPathFromOperationState(AFOperationState state) {
    switch (state) {
        case AFOperationReadyState:
            return @"isReady";
        case AFOperationExecutingState:
            return @"isExecuting";
        case AFOperationFinishedState:
            return @"isFinished";
        case AFOperationPausedState:
            return @"isPaused";
        default: {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
            return @"state";
#pragma clang diagnostic pop
        }
    }
}

并且還復(fù)寫了這些屬性的get方法,用來和自定義的state一一對應(yīng):

//復(fù)寫這些方法,與自己的定義的state對應(yīng)
 - (BOOL)isReady {
    return self.state == AFOperationReadyState && [super isReady];
}
 - (BOOL)isExecuting {
    return self.state == AFOperationExecutingState;
}
 - (BOOL)isFinished {
    return self.state == AFOperationFinishedState;
}
  1. self.lock這個鎖是用來提供給本類一些數(shù)據(jù)操作的線程安全,至于為什么要用遞歸鎖,是因為有些方法可能會存在遞歸調(diào)用的情況,例如有些需要鎖的方法可能會在一個大的操作環(huán)中,形成遞歸。而AF使用了遞歸鎖,避免了這種情況下死鎖的發(fā)生。
  2. 初始化了self.runLoopModes,默認(rèn)為NSRunLoopCommonModes。
  3. 生成了一個默認(rèn)的 self.securityPolicy,關(guān)于這個policy執(zhí)行的https認(rèn)證,可以見樓主之前的文章。

這個類為了自定義operation的各種狀態(tài),而且更好的掌控它的生命周期,復(fù)寫了operationstart方法,當(dāng)這個operation在一個新線程被調(diào)度執(zhí)行的時候,首先就調(diào)入這個start方法中,接下來我們它的實現(xiàn)看看:

- (void)start {
    [self.lock lock];
    
    //如果被取消了就調(diào)用取消的方法
    if ([self isCancelled]) {
        //在AF常駐線程中去執(zhí)行
        [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    //準(zhǔn)備好了,才開始
    else if ([self isReady]) {
        //改變狀態(tài),開始執(zhí)行
        self.state = AFOperationExecutingState;
        [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    //注意,發(fā)起請求和取消請求都是在同一個線程!!包括回調(diào)都是在一個線程
    
    [self.lock unlock];
}

這個方法判斷了當(dāng)前的狀態(tài),是取消還是準(zhǔn)備就緒,然后去調(diào)用了各自對應(yīng)的方法。

  • 注意這些方法都是在另外一個線程中去調(diào)用的,我們來看看這個線程:
 + (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        //添加端口,防止runloop直接退出
        [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;
}

這兩個方法基本上是被許多人舉例用過無數(shù)次了...

  • 這是一個單例,用NSThread創(chuàng)建了一個線程,并且為這個線程添加了一個runloop,并且加了一個NSMachPort,來防止runloop直接退出。
  • 這條線程就是AF用來發(fā)起網(wǎng)絡(luò)請求,并且接受網(wǎng)絡(luò)請求回調(diào)的線程,僅僅就這一條線程(到最后我們來講為什么要這么做)。和我們之前講的AF3.x發(fā)起請求,并且接受請求回調(diào)時的處理方式,遙相呼應(yīng)。

我們接著來看如果準(zhǔn)備就緒,start調(diào)用的方法:

//改變狀態(tài),開始執(zhí)行
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];

接著在常駐線程中,并且不阻塞的方式,在我們self.runLoopModes的模式下調(diào)用:

- (void)operationDidStart {
    [self.lock lock];
    //如果沒取消
    if (![self isCancelled]) {
        //設(shè)置為startImmediately YES 請求發(fā)出,回調(diào)會加入到主線程的 Runloop 下,RunloopMode 會默認(rèn)為 NSDefaultRunLoopMode
        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
        
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        for (NSString *runLoopMode in self.runLoopModes) {
            //把connection和outputStream注冊到當(dāng)前線程runloop中去,只有這樣,才能在這個線程中回調(diào)
            [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
            [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
        }
        //打開輸出流
        [self.outputStream open];
        //開啟請求
        [self.connection start];
    }
    [self.lock unlock];
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];
    });
}

這個方法做了以下幾件事:

  1. 首先這個方法創(chuàng)建了一個NSURLConnection,設(shè)置代理為自己,startImmediately為NO,至于這個參數(shù)干什么用的,我們來看看官方文檔:

startImmediately
YES if the connection should begin loading data immediately, otherwise NO. If you pass NO, the connection is not scheduled with a run loop. You can then schedule the connection in the run loop and mode of your choice by calling scheduleInRunLoop:forMode: .

大意是,這個值默認(rèn)為YES,而且任務(wù)完成的結(jié)果會在主線程的runloop中回調(diào)。如果我們設(shè)置為NO,則需要調(diào)用我們下面看到的:

[self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];

去注冊一個runloop和mode,它會在我們指定的這個runloop所在的線程中回調(diào)結(jié)果。

  1. 值得一提的是這里調(diào)用了:
[self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];

這個outputStream在get方法中被初始化了:

 - (NSOutputStream *)outputStream {
    if (!_outputStream) {
        //一個寫入到內(nèi)存中的流,可以通過NSStreamDataWrittenToMemoryStreamKey拿到寫入后的數(shù)據(jù)
        self.outputStream = [NSOutputStream outputStreamToMemory];
    }
    return _outputStream;
}

這里數(shù)據(jù)請求和拼接并沒有用NSMutableData,而是用了outputStream,而且把寫入的數(shù)據(jù),放到內(nèi)存中。

  • 其實講道理來說outputStream的優(yōu)勢在于下載大文件的時候,可以以流的形式,將文件直接保存到本地,這樣可以為我們節(jié)省很多的內(nèi)存,調(diào)用如下方法設(shè)置:
[NSOutputStream outputStreamToFileAtPath:@"filePath" append:YES];
  • 但是這里是把流寫入內(nèi)存中,這樣其實這個節(jié)省內(nèi)存的意義已經(jīng)不存在了。那為什么還要用呢?這里我猜測的是就是為了用它這個可以注冊在某一個runloop的指定mode下。 雖然AF使用這個outputStream是肯定在這個常駐線程中的,不會有線程安全的問題。但是要注意它是被聲明在.h中的:
@property (nonatomic, strong) NSOutputStream *outputStream;

難保外部不會在其他線程對這個數(shù)據(jù)做什么操作,所以它相對于NSMutableData作用就體現(xiàn)出來了,就算我們在外部其它線程中去操作它,也不會有線程安全的問題。

  1. 這個connection開始執(zhí)行了。
  2. 到主線程發(fā)送一個任務(wù)開始執(zhí)行的通知。
分割圖.png
接下來網(wǎng)絡(luò)請求開始執(zhí)行了,就開始觸發(fā)connection的代理方法了:

代理方法.png

AF2.x一共實現(xiàn)了如上這么多代理方法,這些代理方法,作用大部分和我們之前講的NSURLSession的代理方法類似,我們只挑幾個去講,如果需要了解其他的方法作用,可以參考樓主之前的文章。

重點講下面這四個代理:

注意,有一點需要說明,我們之前是把connection注冊在我們常駐線程的runloop中了,所以以下所有的代理方法,都是在這僅有的一條常駐線程中回調(diào)。

第一個代理
//收到響應(yīng),響應(yīng)頭類似相關(guān)數(shù)據(jù)
- (void)connection:(NSURLConnection __unused *)connection
didReceiveResponse:(NSURLResponse *)response
{
    self.response = response;
}

沒什么好說的,就是收到響應(yīng)后,把response賦給自己的屬性。

第二個代理
//拼接獲取到的數(shù)據(jù)
- (void)connection:(NSURLConnection __unused *)connection
    didReceiveData:(NSData *)data
{
    NSUInteger length = [data length];
    while (YES) {
        NSInteger totalNumberOfBytesWritten = 0;
        //如果outputStream 還有空余空間
        if ([self.outputStream hasSpaceAvailable]) {
           
            //創(chuàng)建一個buffer流緩沖區(qū),大小為data的字節(jié)數(shù)
            const uint8_t *dataBuffer = (uint8_t *)[data bytes];

            NSInteger numberOfBytesWritten = 0;
           
            //當(dāng)寫的長度小于數(shù)據(jù)的長度,在循環(huán)里
            while (totalNumberOfBytesWritten < (NSInteger)length) {
                //往outputStream寫數(shù)據(jù),系統(tǒng)的方法,一次就寫一部分,得循環(huán)寫
                numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)];
                //如果 numberOfBytesWritten寫入失敗了。跳出循環(huán)
                if (numberOfBytesWritten == -1) {
                    break;
                }
                //加上每次寫的長度
                totalNumberOfBytesWritten += numberOfBytesWritten;
            }

            break;
        }
        
        //出錯
        if (self.outputStream.streamError) {
            //取消connection
            [self.connection cancel];
            //調(diào)用失敗的方法
            [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
            return;
        }
    }

    //主線程回調(diào)下載數(shù)據(jù)大小
    dispatch_async(dispatch_get_main_queue(), ^{
        self.totalBytesRead += (long long)length;

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

這個方法看起來長,其實容易理解而且簡單,它只做了3件事:

  1. outputStream拼接數(shù)據(jù),具體如果拼接,大家可以讀注釋自行理解下。
  2. 如果出錯則調(diào)用:connection:didFailWithError:也就是網(wǎng)絡(luò)請求失敗的代理,我們一會下面就會講。
  3. 在主線程中回調(diào)下載進度。
第三個代理
//完成了調(diào)用
- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {

    //從outputStream中拿到數(shù)據(jù) NSStreamDataWrittenToMemoryStreamKey寫入到內(nèi)存中的流
    self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];

    //關(guān)閉outputStream
    [self.outputStream close];
    
    //如果響應(yīng)數(shù)據(jù)已經(jīng)有了,則outputStream置為nil
    if (self.responseData) {
       self.outputStream = nil;
    }
    //清空connection
    self.connection = nil;
    [self finish];
}
  • 這個代理是任務(wù)完成之后調(diào)用。我們從outputStream拿到了最后下載數(shù)據(jù),然后關(guān)閉置空了outputStream。并且清空了connection。調(diào)用了finish:
 - (void)finish {
    [self.lock lock];
    //修改狀態(tài)
    self.state = AFOperationFinishedState;
    [self.lock unlock];

    //發(fā)送完成的通知
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self];
    });
}

把當(dāng)前任務(wù)狀態(tài)改為已完成,并且到主線程發(fā)送任務(wù)完成的通知。,這里我們設(shè)置狀態(tài)為已完成。其實調(diào)用了我們本類復(fù)寫的set的方法(前面遺漏了,在這里補充):

 - (void)setState:(AFOperationState)state {
    
    //判斷從當(dāng)前狀態(tài)到另一個狀態(tài)是不是合理,在加上現(xiàn)在是否取消。。大神的框架就是屌啊,這判斷嚴(yán)謹(jǐn)?shù)?。。一層?    if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
        return;
    }
    
    [self.lock lock];
    
    //拿到對應(yīng)的父類管理當(dāng)前線程周期的key
    NSString *oldStateKey = AFKeyPathFromOperationState(self.state);
    NSString *newStateKey = AFKeyPathFromOperationState(state);
    
    //發(fā)出KVO
    [self willChangeValueForKey:newStateKey];
    [self willChangeValueForKey:oldStateKey];
    _state = state;
    [self didChangeValueForKey:oldStateKey];
    [self didChangeValueForKey:newStateKey];
    [self.lock unlock];
}

這個方法改變state的時候,并且發(fā)送了KVO。大家了解NSOperationQueue就知道,如果對應(yīng)的operation的屬性finnished被設(shè)置為YES,則代表當(dāng)前operation結(jié)束了,會把operation從隊列中移除,并且調(diào)用operationcompletionBlock這點很重要,因為我們請求到的數(shù)據(jù)就是從這個completionBlock中傳遞回去的(下面接著講這個完成Block,就能從這里對接上了)。

第四個代理
//請求失敗的回調(diào),在cancel connection的時候,自己也主動調(diào)用了
- (void)connection:(NSURLConnection __unused *)connection
  didFailWithError:(NSError *)error
{
    //拿到error
    self.error = error;
    //關(guān)閉outputStream
    [self.outputStream close];
    //如果響應(yīng)數(shù)據(jù)已經(jīng)有了,則outputStream置為nil
    if (self.responseData) {
        self.outputStream = nil;
    }
    self.connection = nil;
    [self finish];
}

唯一需要說一下的就是這里給self.error賦值,之后完成Block會根據(jù)這個error,去判斷這次請求是成功還是失敗。

至此我們把AFURLConnectionOperation的業(yè)務(wù)主線講完了。

分割圖.png

我們此時數(shù)據(jù)請求完了,數(shù)據(jù)在self.responseData中,接下來我們來看它是怎么回到我們手里的。
我們回到AFURLConnectionOperation子類AFHTTPRequestOperation,有這么一個方法:

- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    // completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
    self.completionBlock = ^{
        if (self.completionGroup) {
            dispatch_group_enter(self.completionGroup);
        }

        dispatch_async(http_request_operation_processing_queue(), ^{
            if (self.error) {
                if (failure) {
                    dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                        failure(self, self.error);
                    });
                }
            } else {
                id responseObject = self.responseObject;
                if (self.error) {
                    if (failure) {
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            failure(self, self.error);
                        });
                    }
                } else {
                    if (success) {
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            success(self, responseObject);
                        });
                    }
                }
            }

            if (self.completionGroup) {
                dispatch_group_leave(self.completionGroup);
            }
        });
    };
#pragma clang diagnostic pop
}

一開始我們在AFHTTPRequestOperationManager中是調(diào)用過這個方法的:

[operation setCompletionBlockWithSuccess:success failure:failure];
  • 我們在把成功和失敗的Block傳給了這個方法。
  • 這個方法也很好理解,就是設(shè)置我們之前提到過得completionBlock當(dāng)自己數(shù)據(jù)請求完成,就會調(diào)用這個Block。然后我們在這個Block中調(diào)用傳過來的成功或者失敗的Block。如果error為空,說明請求成功,把數(shù)據(jù)傳出去,否則為失敗,把error信息傳出。
  • 這里也類似AF3.x,可以自定義一個完成組和完成隊列。數(shù)據(jù)可以在我們自定義的完成組和隊列中回調(diào)出去。
  • 除此之外,還有一個有意思的地方:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop

之前我們說過,這是在忽略編譯器的一些警告。

  • -Wgnu就不說了,是忽略?:。
  • 值得提下的是-Warc-retain-cycles,這里忽略了循環(huán)引用的警告。我們仔細(xì)看看就知道self持有了completionBlock,而completionBlock內(nèi)部持有self。這里確實循環(huán)引用了。那么AF是如何解決這個循環(huán)引用的呢?

我們在回到AFURLConnectionOperation,還有一個方法我們之前沒講到,它復(fù)寫了setCompletionBlock這個方法。

//復(fù)寫setCompletionBlock
- (void)setCompletionBlock:(void (^)(void))block {
    [self.lock lock];
    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"
            //看有沒有自定義的完成組,否則用AF的組
            dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
            //看有沒有自定義的完成queue,否則用主隊列
            dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop
            
            //調(diào)用設(shè)置的Block,在這個組和隊列中
            dispatch_group_async(group, queue, ^{
                block();
            });

            //結(jié)束時候置nil,防止循環(huán)引用
            dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
                [strongSelf setCompletionBlock:nil];
            });
        }];
    }
    [self.lock unlock];
}

注意,它在我們設(shè)置的block調(diào)用結(jié)束的時候,主動的調(diào)用:

[strongSelf setCompletionBlock:nil];

把Block置空,這樣循環(huán)引用不復(fù)存在了。

好像我們還遺漏了一個東西,就是返回的數(shù)據(jù)做類型的解析。其實還真不是樓主故意這樣?xùn)|一塊西一塊的,AF2.x有些代碼確實是這樣零散。。當(dāng)然僅僅是相對3.x來說。AFNetworking整體代碼質(zhì)量,以及架構(gòu)思想已經(jīng)強過絕大多數(shù)開源項目太多了。。這一點毋庸置疑。

我們來接著看看數(shù)據(jù)解析在什么地方被調(diào)用的把:
- (id)responseObject {
    [self.lock lock];
    if (!_responseObject && [self isFinished] && !self.error) {
        NSError *error = nil;
        //做數(shù)據(jù)解析
        self.responseObject = [self.responseSerializer responseObjectForResponse:self.response data:self.responseData error:&error];
        if (error) {
            self.responseSerializationError = error;
        }
    }
    [self.lock unlock];
    return _responseObject;
}

AFHTTPRequestOperation 復(fù)寫了 responseObject 的get方法,
并且把數(shù)據(jù)按照我們需要的類型(json、xml等等)進行解析。至于如何解析,可以參考樓主之前AF系列的文章,這里就不贅述了。

有些小伙伴可能會說,樓主你是不是把AFSecurityPolicy給忘了啊,其實并沒有,它被在 AFURLConnectionOperation中https認(rèn)證的代理中被調(diào)用,我們之前系列的文章已經(jīng)講的非常詳細(xì)了,感興趣的朋友可以翻到前面的文章去看看。

至此,AF2.x整個業(yè)務(wù)流程就結(jié)束了。

接下來,我們來總結(jié)總結(jié)AF2.x整個業(yè)務(wù)請求的流程:


AF2.x請求流程圖.png

PS.圖片是用page畫的,第一次用,畫了半個小時有沒有...有沒有感受到樓主很走心...最近發(fā)現(xiàn)寫文圖太少了,以后會多配圖的。來加深大家的理解...

如上圖,我們來梳理一下整個流程:
  • 最上層的是AFHTTPRequestOperationManager,我們調(diào)用它進行g(shù)et、post等等各種類型的網(wǎng)絡(luò)請求
  • 然后它去調(diào)用AFURLRequestSerialization做request參數(shù)拼裝。然后生成了一個AFHTTPRequestOperation實例,并把request交給它。然后把AFHTTPRequestOperation添加到一個NSOperationQueue中。
  • 接著AFHTTPRequestOperation拿到request后,會去調(diào)用它的父類AFURLConnectionOperation的初始化方法,并且把相關(guān)參數(shù)交給它,除此之外,當(dāng)父類完成數(shù)據(jù)請求后,它調(diào)用了AFURLResponseSerialization把數(shù)據(jù)解析成我們需要的格式(json、XML等等)。
  • 最后就是我們AF最底層的類AFURLConnectionOperation,它去數(shù)據(jù)請求,并且如果是https請求,會在請求的相關(guān)代理中,調(diào)用AFSecurityPolicy做https認(rèn)證。最后請求到的數(shù)據(jù)返回。

這就是AF2.x整個做網(wǎng)絡(luò)請求的業(yè)務(wù)流程。

我們來解決解決之前遺留下來的問題:為什么AF2.x需要一條常駐線程?

首先如果我們用NSURLConnection,我們?yōu)榱双@取請求結(jié)果有以下三種選擇:

  1. 在主線程調(diào)異步接口
  2. 每一個請求用一個線程,對應(yīng)一個runloop,然后等待結(jié)果回調(diào)。
  3. 只用一條線程,一個runloop,所有結(jié)果回調(diào)在這個線程上。

很顯然AF選擇的是第3種方式,創(chuàng)建了一條常駐線程專門處理所有請求的回調(diào)事件,這個模型跟nodejs有點類似,我們來討論討論不選擇另外兩種方式的原因:

  1. 試想如果我們所有的請求都在主線程中異步調(diào)用,好像沒什么不可以?那為什么AF不這么做呢...在這里有兩點原因(樓主個人總結(jié)的,有不同意見,歡迎討論):
  • 第一是,如果我們放到主線程去做,勢必要這么寫:
 [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES] 

這樣NSURLConnection的回調(diào)會被放在主線程中NSDefaultRunLoopMode中,這樣我們在其它類似UITrackingRunLoopMode模式下,我們是得不到網(wǎng)絡(luò)請求的結(jié)果的,這顯然不是我們想要的,那么我們勢必需要調(diào)用:

[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 

把它加入````NSRunLoopCommonModes```中,試想如果有大量的網(wǎng)絡(luò)請求,同時回調(diào)回來,就會影響我們的UI體驗了。

  • 另外一點原因是,如果我們請求數(shù)據(jù)返回,勢必要進行數(shù)據(jù)解析,解析成我們需要的格式,那么這些解析都在主線程中做,給主線程增加額外的負(fù)擔(dān)。
    又或者我們回調(diào)回來開辟一個新的線程去做數(shù)據(jù)解析,那么我們有n個請求回來開辟n條線程帶來的性能損耗,以及線程間切換帶來的損耗,是不是一筆更大的開銷。

所以綜述兩點原因,我們并不適合在主線程中回調(diào)。

  1. 我們一開始就開辟n條線程去做請求,然后設(shè)置runloop?;钭【€程,等待結(jié)果回調(diào)。
  • 其實看到這,大家想想都覺得這個方法很傻,為了等待不確定的請求結(jié)果,阻塞住線程,白白浪費n條線程的開銷。

綜上所述,這就是AF2.x需要一條常駐線程的原因了

至此我們把AF2.x核心流程分析完了。
分割圖.png

接著到我們本系列一個最終總結(jié)了: AFNetworking到底做了什么?

  • 相信如果從頭看到尾的小伙伴,心里都有了一個屬于自己的答案。其實在樓主心里,實在不想去總結(jié)它,因為AFNetworking中凝聚了太多大牛的思想,根本不是你看完幾遍源碼所能去議論的。但是想想也知道,如果我說不總結(jié),估計有些看到這的朋友殺人的心都有...
  • 所以我還是趕鴨子上架,來總結(jié)總結(jié)它。
AFNetworking的作用總結(jié):

一. 首先我們需要明確一點的是:
相對于AFNetworking2.x,AFNetworking3.x確實沒那么有用了。AFNetworking之前的核心作用就是為了幫我們?nèi)フ{(diào)度所有的請求。但是最核心地方卻被蘋果的NSURLSession給借鑒過去了,嗯...是借鑒。這些請求的調(diào)度,現(xiàn)在完全由NSURLSession給做了,AFNetworking3.x的作用被大大的削弱了。
二. 但是除此之外,其實它還是很有用的:

  1. 首先它幫我們做了各種請求方式request的拼接。想想如果我們用NSURLSession,我們?nèi)プ稣埱?,是不是還得自己去考慮各種請求方式下,拼接參數(shù)的問題。
  • 它還幫我們做了一些公用參數(shù)(session級別的),和一些私用參數(shù)(task級別的)的分離。它用Block的形式,支持我們自定義一些代理方法,如果沒有實現(xiàn)的話,AF還幫我們做了一些默認(rèn)的處理。而如果我們用NSURLSession的話,還得參照AF這么一套代理轉(zhuǎn)發(fā)的架構(gòu)模式去封裝。

  • 它幫我們做了自定義的https認(rèn)證處理??催^樓主之前那篇AFNetworking之于https認(rèn)證的朋友就知道,如果我們自己用NSURLSession實現(xiàn)那幾種自定義認(rèn)證,需要多寫多少代碼...

  • 對于請求到的數(shù)據(jù),AF幫我們做了各種格式的數(shù)據(jù)解析,并且支持我們設(shè)置自定義的code范圍,自定義的數(shù)據(jù)方式。如果不在這些范圍中,則直接調(diào)用失敗block。如果用NSURLSession呢?這些都自己去寫吧...(你要是做過各種除json外其他的數(shù)據(jù)解析,就會知道這里面坑有多少...)

  • 對于成功和失敗的回調(diào)處理。AF幫我們在數(shù)據(jù)請求到,到回調(diào)給用戶之間,做了各種錯誤的判斷,保證了成功和失敗的回調(diào),界限清晰。在這過程中,AF幫我們做了太多的容錯處理,而NSURLSession呢?只給了一個完成的回調(diào),我們得多做多少判斷,才能拿到一個確定能正常顯示的數(shù)據(jù)?

  • ......

  • ...

光是這些網(wǎng)絡(luò)請求的業(yè)務(wù)邏輯,AF幫我們做的就太多太多,當(dāng)然還遠(yuǎn)不僅于此。它用凝聚著許多大牛的經(jīng)驗方式,幫我在有些處理中做了最優(yōu)的選擇,比如我們之前說到的,回調(diào)線程數(shù)設(shè)置為1的問題...幫我們繞開了很多的坑,比如系統(tǒng)內(nèi)部并行創(chuàng)建task導(dǎo)致id不唯一等等...

三. 而如果我們需要一些UIKit的擴展,AF則提供了最穩(wěn)定,而且最優(yōu)化實現(xiàn)方式:

  • 就比如之前說到過得那個狀態(tài)欄小菊花,如果是我們自己去做,得多寫多少代碼,而且實現(xiàn)的還沒有AF那樣質(zhì)量高。
  • 又或者AFImageDownloader,它對于組圖片之間的下載協(xié)調(diào),以及緩存使用的之間線程調(diào)度。對于線程,鎖,以及性能各方面權(quán)衡,找出最優(yōu)化的處理方式,試問小伙伴們自己基于NSURLSession去寫,能到做幾分...

所以最后的結(jié)論是:AFNetworking雖然變?nèi)趿?,但是它還是很有用的。用它真的不僅僅是習(xí)慣,而是因為它確實幫我們做了太多。

寫在最后:
  • 這個系列終于結(jié)束了,從想要開始寫這個系列,到真正結(jié)束,花了大半個月的時間。其實3.x源碼早在剛出來的時候就讀過了,為了寫它,又拿出來重新讀了一遍。而且為了讓大家更容易理解,樓主在大部分代碼,幾乎是一行一個注釋的在標(biāo)注,在這里浪費了大量的時間。
    然而看到大家的贊和評論,還有有些素昧平生的朋友的打賞。讓我發(fā)自內(nèi)心的開心。這一切都太值得...
    如果你能看到這里,除了感謝還是感謝以后還會分享更多好的文章,謝謝
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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