一切的一切要从5百万年前的赛博坦星球大战开始说起,当时我第一次写iOS代码.....错了,重来!啊哈哈哈哈,一切的一切要从一道面试题说起:
AFNetworking 2x版本为什么有一条常驻的线程,作用是啥?3x版本为什么没有?
要回答这道题,必然要对AFNetWorking的2x、3x版本都有所了解。所以下面来解析一波AFNetWorking到底做了啥,从3x解析起吧
AFNetWorking 3x
通过代码结构可以看出,除去Support Files,可以看到AF分为如下5个功能模块:
- 网络通信模块(AFURLSessionManager、AFHTTPSessionManger)
- 网络状态监听模块(Reachability)
- 网络通信安全策略模块(Security)
- 网络通信信息序列化/反序列化模块(Serialization)
- 对于iOS UIKit库的扩展(UIKit)
其核心当然是网络通信模块AFURLSessionManager
其中AFHTTPSessionManager是继承于AFURLSessionManager的,我们一般做网络请求都是用这个类,但是它本身是没有做实事的,只是做了一些简单的封装,把请求逻辑分发给父类AFURLSessionManager或者其它类去做。showCode:
- (instancetype)init {
return [self initWithSessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
self.operationQueue = [[NSOperationQueue alloc] init];
//queue并发线程数设置为1,NSURLSession的代理回调会来到这个queue,因为并发设置为1,所以是个串行的队列。
self.operationQueue.maxConcurrentOperationCount = 1;
//注意代理,代理的继承,实际上NSURLSession去判断了,你实现了哪个方法会去调用,包括子代理的方法!
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
//各种响应转码
self.responseSerializer = [AFJSONResponseSerializer serializer];
//安全策略设置
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
// 设置存储NSURL task与AFURLSessionManagerTaskDelegate的dic(重点,在AFNet中,每一个task都会被匹配一个AFURLSessionManagerTaskDelegate 来做task的delegate事件处理)
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
// 加锁
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
return self;
}
这个是AFURLSessionManager的初始化方法。
那么发送一个get请求的流程是啥?它是调用了AFHTTPSessionManager类中的方法,先去序列化生产对应的NSMutableURLRequest,然后调用父类AFURLSessionManager的方法来发送请求的。
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
return dataTask;
}
那么它生成NSMutableURLRequest 是调用了AFURLRequestSerialization中的方法。
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(method);
NSParameterAssert(URLString);
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
// 将传入的parameters进行编码,并添加到request中,也就是把入参进行序列化,然后拼到url后面或者放到body体里
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
NSString *query = nil;
if (parameters) {
if (self.queryStringSerialization) {
NSError *serializationError;
// 自定义的编码方式对入参编码 (block的方式定义编码自己的规则)
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
// 默认的编码方式对入参编码
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
那么默认的编码方式是如何的呢?
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
// 把单个的入参编码后的字符串用&拼接起来。
return [mutablePairs componentsJoinedByString:@"&"];
}
- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return AFPercentEscapedStringFromString([self.field description]);
} else {
// 最后编码成的字符串的方式,类似key=value的这种字符串
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
}
}
比如编码的结果如下:
假如请求的入参是这样的字典:
@{
@"name" : @"bang",
@"phone": @"13444444444",
@"families": @"myFamilies",
}
编码后的结果:name=bang&phone=13444444444&families=myFamilies
紧接着这个方法还根据该request中请求类型,来判断参数字符串应该如何设置到request中去。如果是GET、HEAD、DELETE,则把参数quey是拼接到url后面的。而POST、PUT是把query拼接到http body中的。
至此,我们生成了一个request。
开始请求数据
有了request后,我们接着调用了父类的生成task的方法,并且执行了一个成功和失败的回调,我们接着去父类AFURLSessionManger里看(总算到我们的核心类了..)
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
__block NSURLSessionDataTask *dataTask = nil;
//第一件事,创建NSURLSessionDataTask,里面适配了Ios8以下taskIdentifiers,函数创建task对象。
//其实现应该是因为iOS 8.0以下版本中会并发地创建多个task对象,而同步有没有做好,导致taskIdentifiers 不唯一…这边做了一个串行处理
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
// 生成了一个AFURLSessionManagerTaskDelegate,这个其实就是AF的自定义代理。我们请求传来的参数,都赋值给这个AF的代理了。
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
//
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
// 将AF delegate放入以taskIdentifier标记的词典中(同一个NSURLSession中的taskIdentifier是唯一的)
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
AFURLSessionManagerTaskDelegate这个类主要是用来处理一些回调的。NSURLSession的代理回调方法中,有一些方法是回调到这个类中去处理的。因为上面的方法把AF代理和task建立映射,存在了一个我们事先声明好的字典里。那么针对不同的请求task的回调,就可以不一定的去处理。就是放在和task一一对应的AFURLSessionManagerTaskDelegate类中去做的。而对于共性的那些代理回调就在本类中去处理。
而要加锁的原因是因为本身我们这个字典属性是mutable的,是线程不安全的。而我们对这些方法的调用,确实是会在复杂的多线程环境中,后面会仔细提到线程问题。
到这里我们整个对task的处理就完成了。
开始处理回调方法。
这里简述一下NSURLSession的各个代理方法具体是干啥的。
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error{}
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
{}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
{}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{}
在上面的的这些回调方法中,有几个是特别的,是回调到AFURLSessionManagerTaskDelegate这个类中来处理的。这里列举其中的2个
// 这个是当请求结束的时候会来到的函数,成功与失败都会来到这。
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
//We no longer need the reference, so nil it out to gain back some memory.
self.mutableData = nil;
}
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
if (error) { // 请求报错了的处理
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
//可以自己自定义完成组 和自定义完成queue,完成回调,不然就是主线程去回调。
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
} else {
// url_session_manager_processing_queue AF的并行队列
dispatch_async(url_session_manager_processing_queue(), ^{
// 请求有数据成功返回,当也有可能返回的数据格式不对,解析失败的请情况。
NSError *serializationError = nil;
// 解析数据
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
//如果是下载文件,那么responseObject为下载的路径
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
//写入userInfo
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
//如果解析错误
if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}
//回调结果 一样如果是有自定义的组和队列,则在自定义的里完成。
dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
}
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
// 接受到数据,通过self.mutableData存起来。
self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;
[self.mutableData appendData:data];
}
至此,数据请求回来,就能正常回调了。
最后我们来梳理一下AF3.x的整个流程和线程的关系:
- 我们一开始初始化
sessionManager
的时候,一般都是在主线程,(当然不排除有些人喜欢在分线程初始化...) - 然后我们调用
get
或者post
等去请求数据,接着会进行request
拼接,AF代理的字典映射,progress
的KVO
添加等等,到NSUrlSession
的resume
之前这些准备工作,仍旧是在主线程中的。 - 然后我们调用
NSUrlSession
的resume
,接着就跑到NSUrlSession
内部去对网络进行数据请求了,在它内部是多线程并发的去请求数据的。 - 紧接着数据请求完成后,回调回来在我们一开始生成的并发数为1的
NSOperationQueue
中,这个时候会是多线程串行的回调回来的。(注:不明白的朋友可以看看雷纯峰大神这篇iOS 并发编程之 Operation Queues) - 然后我们到返回数据解析那一块,我们自己又创建了并发的多线程,去对这些数据进行了各种类型的解析。
- 最后我们如果有自定义的
completionQueue
,则在自定义的queue
中回调回来,也就是分线程回调回来,否则就是主队列,主线程中回调结束。
AFNetWorking 2.x
To be continue......
面试题解惑
为什么2x版本有一条常驻线程
再清楚了2x和3x版本,再来看一开始的面试题,之所以在2x版本中有一条常驻的线程,其原因和2x版本用NSURLConnection来发起请求有关。在NSURLConnection发起请求的方法中有个参数startImmediately,苹果中对它的解释是这个值默认为YES,而且任务完成的结果会在主线程的runloop中回调(代理方法回调的线程)。如果我们设置为NO,我们需要自己去给他设置一个子线程的runloop回调。
// NSURLConnection发起请求。
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
那如果我们用NSURLConnection,我们为了获取请求结果有以下三种选择:
- 在主线程调异步接口
- 每一个请求用一个线程,对应一个runloop,然后等待结果回调。
- 只用一条线程,一个runloop,所有结果回调在这个线程上。
很显然AF选择的是第3种方式,创建了一条常驻线程专门处理所有请求的回调事件。
那么选择第1中有什么缺点呢:
- 如果我们放到主线程去做,那么一般会回到到主线程的runloop的defaultMode中,那么当出现类似tableView滚动的时候,在tracemodel下就会收不到请求。
就算另外写代码放到commonMode下,试想如果有大量的网络请求,同时回调回来,就会影响我们的UI体验了。 - 另外如果我们请求数据返回,势必要进行数据解析,解析成我们需要的格式,那么这些解析都在主线程中做,给主线程增加额外的负担。
又或者我们回调回来开辟一个新的线程去做数据解析,那么我们有n个请求回来开辟n条线程带来的性能损耗,以及线程间切换带来的损耗,是不是一笔更大的开销。
那选择2中方式呢?其实更傻,为了等待不确定的请求结果,阻塞住线程,白白浪费n条线程的开销。(子线程要一直等着回调。)
综上所述,AF2x中有一条常驻线程。
注:NSURLConnection的所有请求是在这个常驻线程中发起的,指定所有的代理回调到该线程中来处理。
AFNetWorking3x版本是怎么做的
在3x版本中,网络请求是通过NSURLSession来发起的。那么情况就和2x完全不一样了。
dataTask = [self.session dataTaskWithRequest:request];
我们来看看self.session 是怎么初始化的。
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
- 从上面的代码可以看出,我们初始化session的时候,先初始化了NSOperationQueue,且设置了operationQueue.maxConcurrentOperationCount = 1;(这里很关键)把session的delegateQueue设置成了我们初始化的NSOperationQueue。那么意味着NSURLSession发起的多线程的网络请求的回调回来在我们一开始生成的并发数为1的NSOperationQueue中,这个时候会是多线程串行的回调回来的。这样和2x版本的常驻线程异曲同工之妙。
- 另外因为跟代理相关的一些操作AF都使用了NSLock。所以就算Queue的并发数设置为n,因为多线程回调,锁的等待,导致所提升的程序速度也并不明显。反而多task回调导致的多线程并发,平白浪费了部分性能。(注:这里虽然回调Queue的并发数为1,当是NSURLSession发起的请求是多线程的,所以还是有多个线程存在的,但是因为是串行回调,所以同一时间,只会有一条线程在操作那些代理。)
网友评论