寫在開頭:
- 大概回憶下,之前我們講了
AFNetworking
整個(gè)網(wǎng)絡(luò)請(qǐng)求的流程,包括request
的拼接,session
代理的轉(zhuǎn)發(fā),response
的解析。以及對(duì)一些bug
的適配,如果你還沒有看過,可以點(diǎn)這里:
AFNetworking到底做了什么?
AFNetworking到底做了什么(二)? - 除此之外我們還單獨(dú)的開了一篇講了AF對(duì)
https
的處理:
AFNetworking之于https認(rèn)證 - 還有一篇講了講AF對(duì)UIKit的擴(kuò)展:
AFNetworking之UIKit擴(kuò)展與緩存實(shí)現(xiàn) - 本文將涉及AF2.x的核心實(shí)現(xiàn),與AF3.x最新版本之間的對(duì)比,以及本系列的一個(gè)最終總結(jié):AFNetworking到底做了什么?
開始正文
首先我們來看看AF2.x的項(xiàng)目目錄:
除了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;
}
初始化方法中,初始化了一些屬性,下面我們來簡單的介紹一下這些屬性:
-
_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;
}
-
self.lock
這個(gè)鎖是用來提供給本類一些數(shù)據(jù)操作的線程安全,至于為什么要用遞歸鎖,是因?yàn)橛行┓椒赡軙?huì)存在遞歸調(diào)用的情況,例如有些需要鎖的方法可能會(huì)在一個(gè)大的操作環(huán)中,形成遞歸。而AF使用了遞歸鎖,避免了這種情況下死鎖的發(fā)生。 - 初始化了
self.runLoopModes
,默認(rèn)為NSRunLoopCommonModes
。 - 生成了一個(gè)默認(rèn)的
self.securityPolicy
,關(guān)于這個(gè)policy執(zhí)行的https認(rèn)證,可以見樓主之前的文章。
這個(gè)類為了自定義operation
的各種狀態(tài),而且更好的掌控它的生命周期,復(fù)寫了operation
的start
方法,當(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è)方法做了以下幾件事:
- 首先這個(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é)果。
- 值得一提的是這里調(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ì)有線程安全的問題。
- 這個(gè)
connection
開始執(zhí)行了。 - 到主線程發(fā)送一個(gè)任務(wù)開始執(zhí)行的通知。
接下來網(wǎng)絡(luò)請(qǐng)求開始執(zhí)行了,就開始觸發(fā)connection
的代理方法了:
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件事:
- 給
outputStream
拼接數(shù)據(jù),具體如果拼接,大家可以讀注釋自行理解下。 - 如果出錯(cuò)則調(diào)用:
connection:didFailWithError:
也就是網(wǎng)絡(luò)請(qǐng)求失敗的代理,我們一會(huì)下面就會(huì)講。 - 在主線程中回調(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)用operation
的completionBlock
。這點(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ù)主線講完了。
我們此時(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)求的流程:
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é)果有以下三種選擇:
- 在主線程調(diào)異步接口
- 每一個(gè)請(qǐng)求用一個(gè)線程,對(duì)應(yīng)一個(gè)runloop,然后等待結(jié)果回調(diào)。
- 只用一條線程,一個(gè)runloop,所有結(jié)果回調(diào)在這個(gè)線程上。
很顯然AF選擇的是第3種方式,創(chuàng)建了一條常駐線程專門處理所有請(qǐng)求的回調(diào)事件,這個(gè)模型跟nodejs
有點(diǎn)類似,我們來討論討論不選擇另外兩種方式的原因:
- 試想如果我們所有的請(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)。
- 我們一開始就開辟n條線程去做請(qǐng)求,然后設(shè)置runloop?;钭【€程,等待結(jié)果回調(diào)。
- 其實(shí)看到這,大家想想都覺得這個(gè)方法很傻,為了等待不確定的請(qǐng)求結(jié)果,阻塞住線程,白白浪費(fèi)n條線程的開銷。
綜上所述,這就是AF2.x需要一條常駐線程的原因了。
至此我們把AF2.x核心流程分析完了。
接著到我們本系列一個(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í)它還是很有用的:
-
首先它幫我們做了各種請(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ì)分享更多好的文章,謝謝