概述
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的使用,以及如何实现一个网络请求的处理流程,对于初学者来说,具有参考和学习的价值。