概述
AFNetwokring目前是3.x版本,基于NSURLSession的功能進行封裝,而2.x版本是基于NSURLConnection。由于NSURLConnection逐漸被NSURLSession所取代,2.x版本逐漸被3.x取代。本篇分析一下2.x版本,因為該版本涉及的一些代碼值得學習,例如NSOperation、KVO的使用。
AFHTTPRequestOperationManager
AFHTTPRequestOperationManager是AFN封裝的管理HTTP請求的類,首先初始化方法中設置了一些參數值,代碼注釋如下:
- (instancetype)initWithBaseURL:(NSURL *)url {
...
self.baseURL = url;
self.requestSerializer = [AFHTTPRequestSerializer serializer]; //序列化
self.responseSerializer = [AFJSONResponseSerializer serializer];//反序列化
self.securityPolicy = [AFSecurityPolicy defaultPolicy]; //默認的安全策略
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];//監聽網絡狀態
self.operationQueue = [[NSOperationQueue alloc] init]; //任務隊列
self.shouldUseCredentialStorage = YES;
return self;
}
初始化方法設置了請求報文序列化/反序列化對象,以及默認的安全策略,網絡監聽對象,任務隊列。
AFHTTPRequestOperationManager提供了一系列HTTP請求相關的方法,例如GET、POST、PATCH等,內部實現相同,只是method參數值不同,以GET請求方法為例,代碼注釋如下:
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
首先創建一個AFHTTPRequestOperation類型的NSOperation對象,然后將NSOperation對象加入operationQueue隊列中,開始執行operation。在創建AFHTTPRequestOperation對象的方法中,首先通過requestSerializer構建NSURLRequest對象,然后設置相關屬性,設置completionBlock,代碼注釋如下:
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
... //設置相關屬性
[operation setCompletionBlockWithSuccess:success failure:failure]; //設置operation結束時的completionBlock
operation.completionQueue = self.completionQueue; //執行completionBlock的隊列
operation.completionGroup = self.completionGroup; //執行completionBlock的group
return operation;
}
AFHTTPRequestOperation繼承AFURLConnectionOperation,AFURLConnectionOperation真正負責網絡請求的發出以及處理,AFHTTPRequestOperation設置completionBlock的方法如下:
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
#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) { //網絡請求失敗
//在completionQueue或者主線程隊列中執行失敗block
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) {
//反序列化報文數據失敗,在completionQueue或者主線程隊列中執行失敗block
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
//反序列化報文數據成功,在completionQueue或者主線程隊列中執行成功block
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
}
該方法定義一個void (^)(void)類型的block,設置給父類的completionBlock屬性,當執行completionBlock時,首先會判斷網絡請求是否錯誤,如果出錯,直接在completionQueue或者主線程中調用failure的block拋給調用層。如果網絡請求成功,則調用responseObject方法反序列化響應報文數據responseData,代碼注釋如下:
- (id)responseObject {
[self.lock lock];
if (!_responseObject && [self isFinished] && !self.error) {
NSError *error = nil;
//反序列化響應報文數據
self.responseObject = [self.responseSerializer responseObjectForResponse:self.response data:self.responseData error:&error];
if (error) {
self.responseSerializationError = error;
}
}
[self.lock unlock];
return _responseObject;
}
由于該方法在http_request_operation_processing_queue()中執行,不影響主線程的性能。如果反序列化成功,調用success的blcok將error拋給調用層,如果失敗,調用failure的block將反序列化后的對象拋給調用層。
AFURLConnectionOperation
AFURLConnectionOperation負責發送網絡請求,處理delegate回調方法。AFURLConnectionOperation繼承NSOperation,眾所周知,當實現一個自定義的NSOperation時,需要重寫NSOperation的相關方法,以確保operation機制的正常運行。
初始化方法
初始化方法設置了相關參數,代碼注釋如下:
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
NSParameterAssert(urlRequest);
self = [super init];
if (!self) {
return nil;
}
_state = AFOperationReadyState; //狀態設置為準備執行
self.lock = [[NSRecursiveLock alloc] init]; //創建遞歸鎖
self.lock.name = kAFNetworkingLockName;
self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
self.request = urlRequest; //設置urlRequest
self.shouldUseCredentialStorage = YES;
self.securityPolicy = [AFSecurityPolicy defaultPolicy]; //設置securityPolicy
return self;
}
該方法設置用于網絡請求的request,初始化了遞歸鎖,安全策略對象securityPolicy,同時設置了狀態為AFOperationReadyState(準備執行)。
狀態機制
當operation加入到operationQueue中時,operationQueue會通過KVO的方式監聽operation的狀態,NSOperation有幾種狀態,分別對應以下屬性:
isReady(是否準備執行)
isExecuting(是否正在執行)
isCancelled(是否取消)
isPaused(是否暫停)
isFinished(是否完成)
上面的屬性狀態決定operation的生命周期,operationQueue監聽operation的狀態屬性,當operation的isFinished屬性為YES時,說明operation生命周期結束,operation會在隊列中被釋放。AFURLConnectionOperation實現了自定義的operation,重寫了以下屬性方法:
- (BOOL)isReady {
return self.state == AFOperationReadyState && [super isReady];
}
- (BOOL)isExecuting {
return self.state == AFOperationExecutingState;
}
- (BOOL)isFinished {
return self.state == AFOperationFinishedState;
}
通過getter方法訪問operation的屬性時,返回的狀態值會根據AFNetworking維護的枚舉值來確定。下面是枚舉類型AFOperationState的代碼:
typedef NS_ENUM(NSInteger, AFOperationState) {
AFOperationPausedState = -1, //暫停
AFOperationReadyState = 1, //準備執行
AFOperationExecutingState = 2, //正在執行
AFOperationFinishedState = 3, //完成
};
分別對應operation的狀態屬性,同時實現-setState:方法來更新AFOperationState的枚舉值,下面是代碼注釋:
- (void)setState:(AFOperationState)state {
if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
return;
}
[self.lock lock];
NSString *oldStateKey = AFKeyPathFromOperationState(self.state); //原狀態
NSString *newStateKey = AFKeyPathFromOperationState(state); //新狀態
[self willChangeValueForKey:newStateKey]; //手動發送KVO通知
[self willChangeValueForKey:oldStateKey]; //手動發送KVO通知
_state = state; //切換狀態
[self didChangeValueForKey:oldStateKey]; //手動發送KVO通知
[self didChangeValueForKey:newStateKey]; //手動發送KVO通知
[self.lock unlock];
}
首先通過AFKeyPathFromOperationState方法將新舊AFOperationState枚舉值映射成operation的狀態屬性名,然后更新AFOperationState枚舉值,當外界訪問operation的狀態屬性時,狀態已經改變。同時手動發送KVO通知,通知operationQueue,operation的狀態屬性發生了改變。
start方法
如果實現自定義的NSOperation,需要重寫start方法,下面是代碼注釋:
- (void)start {
[self.lock lock];
if ([self isCancelled]) { //如果operation之前被取消,調用cancelConnection方法取消connection
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) { //如果operation準備執行,調用operationDidStart方法開始構建connection,發送網絡請求
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
該方法首先判斷operation當前的狀態,如果之前已經被取消了,則調用cancelConnection方法進一步處理,該方法放在后文分析。如果新建operation,在初始化方法中,設置初始狀態是AFOperationReadyState,即operation的狀態isReady=YES,調用operationDidStart方法開始進行網絡請求。同時AFNetworking創建了一個常駐線程來執行connection相關的方法。常駐線程由類方法networkRequestThread創建,代碼如下:
+ (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;
}
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; //為runloop添加port,使runloop永不退出
[runLoop run];
}
}
首先在dispatch_once()中創建一個線程,由于dispatch_once中的block只會執行一次,所以線程只會創建一次,且_networkRequestThread是static類型的,所以會常駐內存不被釋放。每次調用networkRequestThread方法都會返回該線程指針。由于是手動創建的子線程,需要手動開啟它的runloop,并且在runloop中添加port,使其永不退出。我們將這個常駐線程稱為AFN線程。在AFN線程中執行operationDidStart方法,下面是代碼注釋:
- (void)operationDidStart {
[self.lock lock];
if (![self isCancelled]) {
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.outputStream open];
[self.connection start];
}
[self.lock unlock];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];
});
}
首先創建了connection,設置當前operation對象為connection對象的delegate,處理網絡請求回調的各個方法。如果startImmediately參數為YES,connection會立刻啟動,開始下載數據,且connection在當前線程的runloop中執行,如果為NO,暫不開始請求數據,需要調用start方法手動開始,同時調用scheduleInRunLoop:forMode:方法把NSURLConnection加入到指定線程的run loop中去運行,否則會加入當前線程的runloop中去,使用outputStream來接收網絡請求回來的數據。
NSURLConnectionDelegate
在connection網絡請求的過程中,將回調方法拋給delegate執行,下面分析一下主要方法:
-
-(void)connection:willSendRequestForAuthenticationChallenge:方法
當客戶端發送HTTPS請求給服務端時,會進行SSL握手,在握手的過程中,服務端需要客戶端進行授權的響應,客戶端對服務端發來的信息進行校驗,在iOS代碼中,抽象為系統拋出delegate方法給上層代碼,同時傳入一個challenge對象,封裝了需要驗證的信息,下面是代碼部分注釋:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { ... if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { //對serverTrust對象和host進行校驗 if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { //校驗通過,生成一個憑證對象credential NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; //使用credential給系統 [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; } else { //驗證失敗,取消后續SSL連接 [[challenge sender] cancelAuthenticationChallenge:challenge]; } } else { if ([challenge previousFailureCount] == 0) { if (self.credential) { //直接用現有的credential給系統 [[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge]; } else { [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } } else { [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } } }
challenge中的屬性protectionSpace是一個NSURLProtectionSpace對象,包含服務器的host、port、isProxy等信息,同時包含authenticationMethod,即校驗方式的類型,如果類型是NSURLAuthenticationMethodServerTrust,則驗證信任對象serverTrust,如果驗證通過,生成一個憑證對象credential返回給服務端,具體的流程可以參考騰訊Bugly的文章。
-
-(void)connection: didReceiveData:
當網絡連接建立后,服務器開始向客戶端傳輸數據,系統會回調該方法,上層代碼負責接收并且拼裝response數據。下面是部分代碼注釋:
- (void)connection:(NSURLConnection __unused *)connection didReceiveData:(NSData *)data { NSUInteger length = [data length]; //需要讀取的字節長度 while (YES) { NSInteger totalNumberOfBytesWritten = 0; //本次一共寫入的字節長度 if ([self.outputStream hasSpaceAvailable]) { //outputStream有空間寫入 const uint8_t *dataBuffer = (uint8_t *)[data bytes]; NSInteger numberOfBytesWritten = 0; while (totalNumberOfBytesWritten < (NSInteger)length) { numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)]; //將dataBuffer中的數據寫入outputStream中 if (numberOfBytesWritten == -1) { break; } totalNumberOfBytesWritten += numberOfBytesWritten; //累加寫入的字節長度 } break; } else { //outputStream沒有空間寫入 [self.connection cancel]; if (self.outputStream.streamError) { [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError]; } return; } } ... }
該方法主要將data數據寫入outputStream中,對于本次數據data,如果一次性寫不全進outputStream,則通過totalNumberOfBytesWritten記錄共寫入的字節長度,通過while循環控制,直到全部寫入。outputStream通過[NSOutputStream outputStreamToMemory]創建,是寫入內存的流對象,如果hasSpaceAvailable返回NO,即后續返回的response數據沒有空間存放,則直接斷開網絡請求。
-
-(void)connectionDidFinishLoading:
當網絡請求結束時,調用該方法,獲取最終的response數據,結束本次operation。
- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection { self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; //獲取response數據 [self.outputStream close]; //關閉outputStream if (self.responseData) { self.outputStream = nil; } self.connection = nil; [self finish]; //結束operation }
-
-(NSCachedURLResponse *)connection: willCacheResponse:
如果服務端需要將response數據緩存到客戶端的NSURLCache緩存系統,在緩存到客戶端本地之前,會首先調用該方法,可以修改緩存的數據,默認是接口返回的response數據。
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { if (self.cacheResponse) { return self.cacheResponse(connection, cachedResponse); //修改服務端返回緩存數據 } else { if ([self isCancelled]) { return nil; } return cachedResponse; } }
默認網絡請求的緩存策略是UseProtocolCachePolicy,根據服務端返回的Cache-Control字段來開啟HTTP緩存功能,字段值可能包含 max-age,是公共 public 還是私有 private,或者不緩存no-cache 等信息。關于NSURLCache的相關講解,可以參考這篇文章。
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { if (self.cacheResponse) { return self.cacheResponse(connection, cachedResponse); //修改需要緩存的數據 } else { if ([self isCancelled]) { return nil; } return cachedResponse; } }
控制生命周期
上文所述,AFURLConnectionOperation通過setState:方法實現了operation狀態的切換,從而控制operation的生命周期。下面分析一下,另外幾個方法:
-
finish方法
當網絡請求結束時,該方法被調用,負責結束operation的生命周期:
- (void)finish { [self.lock lock]; self.state = AFOperationFinishedState; //設置結束狀態,結束operation的生命周期 [self.lock unlock]; ... }
在setState方法里更改為AFOperationFinishedState狀態并且手動觸發KVO,通知operationQueue,isFinished屬性變化,觸發completionBlock,執行block里面的代碼。
-
cancel方法
該方法取消一個cancel這個operation,同時調用cancelConnection方法取消當前的connection連接。
- (void)cancel { [self.lock lock]; if (![self isFinished] && ![self isCancelled]) { [super cancel]; //調用NSOperation的cancel方法 if ([self isExecuting]) { [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } } [self.lock unlock]; }
注意cancelConnection方法也會在start方法中調用,新建一個請求時,當發現這個operation之前被取消了,進一步判斷connection是否建立,如果connection存在,先取消connection,然后調用finish方法,結束operation的生命周期。
-
pause方法和resume方法
AFURLConnectionOperation提供了pause和resume方法,pause方法將狀態改為AFOperationPausedState,同時取消當前的connection。resume方法將狀態重新改為AFOperationReadyState,同時調用start方法,重新請求connection,將狀態改為AFOperationExecutingState。下面是代碼注釋:
- (void)pause { if ([self isPaused] || [self isFinished] || [self isCancelled]) { return; } [self.lock lock]; if ([self isExecuting]) { //取消當前conneciton [self performSelector:@selector(operationDidPause) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; ... self.state = AFOperationPausedState; //暫停operation [self.lock unlock]; } - (void)resume { if (![self isPaused]) { return; } [self.lock lock]; self.state = AFOperationReadyState; //operation重置為isReady狀態 [self start]; //新建connection,重新請求數據,狀態職位isExecuting [self.lock unlock]; }
pause方法只是取消本次網絡請求,不會結束operation的生命周期,當外界調用resume方法時,也只是重新進行網絡請求。
小結
雖然NSURLConnection及其基礎上封裝的AF2.x版本逐漸被廢棄,但是作者關于operation的使用,以及如何實現一個網絡請求的處理流程,對于初學者來說,具有參考和學習的價值。