AFNetworking到底做了什么?(終)

寫在開頭:
開始正文

首先我們來看看AF2.x的項(xiàng)目目錄:

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

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

1)AFURLConnectionOperation
2)AFHTTPRequestOperation
3)AFHTTPRequestOperationManager

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

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

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

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

接著我們來看看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)度所有請(qǐng)求的queue
    self.operationQueue = [[NSOperationQueue alloc] init];
    //是否做證書驗(yàn)證
    self.shouldUseCredentialStorage = YES;
    return self;
}

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

然后我們來看看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生成了一個(gè)request,至于如何生成,可以參考之前的文章,這里就不贅述了。
2)生成了一個(gè)AFHTTPRequestOperation,然后把這個(gè)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)建了一個(gè)AFHTTPRequestOperation,并把自己的一些參數(shù)交給了這個(gè)operation處理。

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

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

除了設(shè)置了一個(gè)self.responseSerializer,實(shí)際上是調(diào)用了父類,也是我們最核心的類AFURLConnectionOperation的初始化方法,首先我們要明確這個(gè)類是繼承自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)該咨詢證書存儲(chǔ)連接
    self.shouldUseCredentialStorage = YES;

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

    return self;
}

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

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

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

//映射這個(gè)operation的各個(gè)狀態(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一一對(duì)應(yīng):

//復(fù)寫這些方法,與自己的定義的state對(duì)應(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這個(gè)鎖是用來提供給本類一些數(shù)據(jù)操作的線程安全,至于為什么要用遞歸鎖,是因?yàn)橛行┓椒赡軙?huì)存在遞歸調(diào)用的情況,例如有些需要鎖的方法可能會(huì)在一個(gè)大的操作環(huán)中,形成遞歸。而AF使用了遞歸鎖,避免了這種情況下死鎖的發(fā)生
  2. 初始化了self.runLoopModes,默認(rèn)為NSRunLoopCommonModes。
  3. 生成了一個(gè)默認(rèn)的 self.securityPolicy,關(guān)于這個(gè)policy執(zhí)行的https認(rèn)證,可以見樓主之前的文章。

這個(gè)類為了自定義operation的各種狀態(tài),而且更好的掌控它的生命周期,復(fù)寫了operationstart方法,當(dāng)這個(gè)operation在一個(gè)新線程被調(diào)度執(zhí)行的時(shí)候,首先就調(diào)入這個(gè)start方法中,接下來我們它的實(shí)現(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ā)起請(qǐng)求和取消請(qǐng)求都是在同一個(gè)線程!!包括回調(diào)都是在一個(gè)線程
    
    [self.lock unlock];
}

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

  • 注意這些方法都是在另外一個(gè)線程中去調(diào)用的,我們來看看這個(gè)線程:
 + (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;
}

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

  • 這是一個(gè)單例,用NSThread創(chuàng)建了一個(gè)線程,并且為這個(gè)線程添加了一個(gè)runloop,并且加了一個(gè)NSMachPort,來防止runloop直接退出。
  • 這條線程就是AF用來發(fā)起網(wǎng)絡(luò)請(qǐng)求,并且接受網(wǎng)絡(luò)請(qǐng)求回調(diào)的線程,僅僅就這一條線程(到最后我們來講為什么要這么做)。和我們之前講的AF3.x發(fā)起請(qǐng)求,并且接受請(qǐng)求回調(diào)時(shí)的處理方式,遙相呼應(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 請(qǐng)求發(fā)出,回調(diào)會(huì)加入到主線程的 Runloop 下,RunloopMode 會(huì)默認(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注冊(cè)到當(dāng)前線程runloop中去,只有這樣,才能在這個(gè)線程中回調(diào)
            [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
            [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
        }
        //打開輸出流
        [self.outputStream open];
        //開啟請(qǐng)求
        [self.connection start];
    }
    [self.lock unlock];
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];
    });
}

這個(gè)方法做了以下幾件事:

  1. 首先這個(gè)方法創(chuàng)建了一個(gè)NSURLConnection,設(shè)置代理為自己,startImmediately為NO,至于這個(gè)參數(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: .

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

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

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

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

這個(gè)outputStream在get方法中被初始化了:

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

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

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

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

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

代理方法.png

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

重點(diǎn)講下面這四個(gè)代理:

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

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

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

第二個(gè)代理
//拼接獲取到的數(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)建一個(gè)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;
        }
        
        //出錯(cuò)
        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);
        }
    });
}

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

  1. outputStream拼接數(shù)據(jù),具體如果拼接,大家可以讀注釋自行理解下。
  2. 如果出錯(cuò)則調(diào)用:connection:didFailWithError:也就是網(wǎng)絡(luò)請(qǐng)求失敗的代理,我們一會(huì)下面就會(huì)講。
  3. 在主線程中回調(diào)下載進(jìn)度。
第三個(gè)代理
//完成了調(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];
}
  • 這個(gè)代理是任務(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ù)完成的通知。,這里我們?cè)O(shè)置狀態(tài)為已完成。其實(shí)調(diào)用了我們本類復(fù)寫的set的方法(前面遺漏了,在這里補(bǔ)充):

 - (void)setState:(AFOperationState)state {
    
    //判斷從當(dāng)前狀態(tài)到另一個(gè)狀態(tài)是不是合理,在加上現(xiàn)在是否取消。。大神的框架就是屌啊,這判斷嚴(yán)謹(jǐn)?shù)?。。一層?    if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
        return;
    }
    
    [self.lock lock];
    
    //拿到對(duì)應(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];
}

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

第四個(gè)代理
//請(qǐng)求失敗的回調(diào),在cancel connection的時(shí)候,自己也主動(dòng)調(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會(huì)根據(jù)這個(gè)error,去判斷這次請(qǐng)求是成功還是失敗。

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

分割圖.png

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

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

一開始我們?cè)?code>AFHTTPRequestOperationManager中是調(diào)用過這個(gè)方法的:

[operation setCompletionBlockWithSuccess:success failure:failure];
  • 我們?cè)诎殉晒褪〉腂lock傳給了這個(gè)方法。
  • 這個(gè)方法也很好理解,就是設(shè)置我們之前提到過得completionBlock,當(dāng)自己數(shù)據(jù)請(qǐng)求完成,就會(huì)調(diào)用這個(gè)Block。然后我們?cè)谶@個(gè)Block中調(diào)用傳過來的成功或者失敗的Block。如果error為空,說明請(qǐng)求成功,把數(shù)據(jù)傳出去,否則為失敗,把error信息傳出。
  • 這里也類似AF3.x,可以自定義一個(gè)完成組和完成隊(duì)列。數(shù)據(jù)可以在我們自定義的完成組和隊(duì)列中回調(diào)出去。
  • 除此之外,還有一個(gè)有意思的地方:
#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。這里確實(shí)循環(huán)引用了。那么AF是如何解決這個(gè)循環(huán)引用的呢?

我們?cè)诨氐?code>AFURLConnectionOperation,還有一個(gè)方法我們之前沒講到,它復(fù)寫了setCompletionBlock這個(gè)方法。

//復(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,否則用主隊(duì)列
            dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop
            
            //調(diào)用設(shè)置的Block,在這個(gè)組和隊(duì)列中
            dispatch_group_async(group, queue, ^{
                block();
            });

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

注意,它在我們?cè)O(shè)置的block調(diào)用結(jié)束的時(shí)候,主動(dòng)的調(diào)用:

[strongSelf setCompletionBlock:nil];

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

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

我們來接著看看數(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等等)進(jìn)行解析。至于如何解析,可以參考樓主之前AF系列的文章,這里就不贅述了。

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

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

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


AF2.x請(qǐng)求流程圖.png

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

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

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

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

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

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

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

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

這樣NSURLConnection的回調(diào)會(huì)被放在主線程中NSDefaultRunLoopMode中,這樣我們?cè)谄渌愃?code>UITrackingRunLoopMode模式下,我們是得不到網(wǎng)絡(luò)請(qǐng)求的結(jié)果的,這顯然不是我們想要的,那么我們勢必需要調(diào)用:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • ......

  • ...

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

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

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

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

寫在最后:
  • 這個(gè)系列終于結(jié)束了,從想要開始寫這個(gè)系列,到真正結(jié)束,花了大半個(gè)月的時(shí)間。其實(shí)3.x源碼早在剛出來的時(shí)候就讀過了,為了寫它,又拿出來重新讀了一遍。而且為了讓大家更容易理解,樓主在大部分代碼,幾乎是一行一個(gè)注釋的在標(biāo)注,在這里浪費(fèi)了大量的時(shí)間。
    然而看到大家的贊和評(píng)論,還有有些素昧平生的朋友的打賞。讓我發(fā)自內(nèi)心的開心。這一切都太值得...
    如果你能看到這里,除了感謝還是感謝以后還會(huì)分享更多好的文章,謝謝
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,530評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評(píng)論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,759評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,204評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,415評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,955評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評(píng)論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,675評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,967評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容