接着上一篇的内容往下讲,如果没看过上一篇内容可以点这:

AFNetworking到底做了什么?

之前我们讲到NSUrlSession代理这一块:

代理8:
/*
task完成之后的回调,成功和失败都会回调这里
函数讨论:
注意这里的error不会报告服务期端的error,他表示的是客户端这边的eroor,比如无法解析hostname或者连不上host主机。
*/
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
//根据task去取我们一开始创建绑定的delegate
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; // delegate may be nil when completing a task in the background
if (delegate) {
//把代理转发给我们绑定的delegate
[delegate URLSession:session task:task didCompleteWithError:error];
//转发完移除delegate
[self removeDelegateForTask:task];
} //自定义Block回调
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}

这个代理就是task完成了的回调,方法内做了下面这几件事:

  • 在这里我们拿到了之前和这个task对应绑定的AF的delegate:

    - (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task); AFURLSessionManagerTaskDelegate *delegate = nil;
    [self.lock lock];
    delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
    [self.lock unlock]; return delegate;
    }
  • 去转发了调用了AF代理的方法。这个等我们下面讲完NSUrlSession的代理之后会详细说。
  • 然后把这个AF的代理和task的绑定解除了,并且移除了相关的progress和通知:
    - (void)removeDelegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);
    //移除跟AF代理相关的东西
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    [self.lock lock];
    [delegate cleanUpProgressForTask:task];
    [self removeNotificationObserverForTask:task];
    [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
    [self.lock unlock];
    }
  • 调用了自定义的Blcok:self.taskDidComplete(session, task, error);
    代码还是很简单的,至于这个通知,我们等会再来补充吧。
NSURLSessionDataDelegate:
代理9:
//收到服务器响应后调用
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
//设置默认为继续进行
NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow; //自定义去设置
if (self.dataTaskDidReceiveResponse) {
disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
} if (completionHandler) {
completionHandler(disposition);
}
}

官方文档翻译如下:

函数作用:
告诉代理,该data task获取到了服务器端传回的最初始回复(response)。注意其中的completionHandler这个block,通过传入一个类型为NSURLSessionResponseDisposition的变量来决定该传输任务接下来该做什么:
NSURLSessionResponseAllow 该task正常进行
NSURLSessionResponseCancel 该task会被取消
NSURLSessionResponseBecomeDownload 会调用URLSession:dataTask:didBecomeDownloadTask:方法来新建一个download task以代替当前的data task
NSURLSessionResponseBecomeStream 转成一个StreamTask

函数讨论:
该方法是可选的,除非你必须支持“multipart/x-mixed-replace”类型的content-type。因为如果你的request中包含了这种类型的content-type,服务器会将数据分片传回来,而且每次传回来的数据会覆盖之前的数据。每次返回新的数据时,session都会调用该函数,你应该在这个函数中合理地处理先前的数据,否则会被新数据覆盖。如果你没有提供该方法的实现,那么session将会继续任务,也就是说会覆盖之前的数据。

总结一下:

  • 当你把添加content-type的类型为multipart/x-mixed-replace那么服务器的数据会分片的传回来。然后这个方法是每次接受到对应片响应的时候会调被调用。你可以去设置上述4种对这个task的处理。
  • 如果我们实现了自定义Block,则调用一下,不然就用默认的NSURLSessionResponseAllow方式。
代理10:
//上面的代理如果设置为NSURLSessionResponseBecomeDownload,则会调用这个方法
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
//因为转变了task,所以要对task做一个重新绑定
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
if (delegate) {
[self removeDelegateForTask:dataTask];
[self setDelegate:delegate forTask:downloadTask];
}
//执行自定义Block
if (self.dataTaskDidBecomeDownloadTask) {
self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
}
}
  • 这个代理方法是被上面的代理方法触发的,作用就是新建一个downloadTask,替换掉当前的dataTask。所以我们在这里做了AF自定义代理的重新绑定操作。
  • 调用自定义Block。

按照顺序来,其实还有个AF没有去实现的代理:

//AF没实现的代理
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask;

这个也是之前的那个代理,设置为NSURLSessionResponseBecomeStream则会调用到这个代理里来。会新生成一个NSURLSessionStreamTask来替换掉之前的dataTask。

代理11:
//当我们获取到数据就会调用,会被反复调用,请求到的数据就在这被拼装完整
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
[delegate URLSession:session dataTask:dataTask didReceiveData:data];
if (self.dataTaskDidReceiveData) {
self.dataTaskDidReceiveData(session, dataTask, data);
}
}
  • 这个方法和上面didCompleteWithError算是NSUrlSession的代理中最重要的两个方法了。
  • 我们转发了这个方法到AF的代理中去,所以数据的拼接都是在AF的代理中进行的。这也是情理中的,毕竟每个响应数据都是对应各个task,各个AF代理的。在AFURLSessionManager都只是做一些公共的处理。
代理12:
/*当task接收到所有期望的数据后,session会调用此代理方法。
*/
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
{
NSCachedURLResponse *cachedResponse = proposedResponse; if (self.dataTaskWillCacheResponse) {
cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse);
}
if (completionHandler) {
completionHandler(cachedResponse);
}
}

官方文档翻译如下:

函数作用:
询问data task或上传任务(upload task)是否缓存response。

函数讨论:
当task接收到所有期望的数据后,session会调用此代理方法。如果你没有实现该方法,那么就会使用创建session时使用的configuration对象决定缓存策略。这个代理方法最初的目的是为了阻止缓存特定的URLs或者修改NSCacheURLResponse对象相关的userInfo字典。
该方法只会当request决定缓存response时候调用。作为准则,responses只会当以下条件都成立的时候返回缓存:
该request是HTTP或HTTPS URL的请求(或者你自定义的网络协议,并且确保该协议支持缓存)
确保request请求是成功的(返回的status code为200-299)
返回的response是来自服务器端的,而非缓存中本身就有的
提供的NSURLRequest对象的缓存策略要允许进行缓存
服务器返回的response中与缓存相关的header要允许缓存
该response的大小不能比提供的缓存空间大太多(比如你提供了一个磁盘缓存,那么response大小一定不能比磁盘缓存空间还要大5%)

  • 总结一下就是一个用来缓存response的方法,方法中调用了我们自定义的Block,自定义一个response用来缓存。
NSURLSessionDownloadDelegate
代理13:
//下载完成的时候调用

- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
//这个是session的,也就是全局的,后面的个人代理也会做同样的这件事
if (self.downloadTaskDidFinishDownloading) { //调用自定义的block拿到文件存储的地址
NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (fileURL) {
delegate.downloadFileURL = fileURL;
NSError *error = nil;
//从临时的下载路径移动至我们需要的路径
[[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
//如果移动出错
if (error) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
}
return;
}
}
//转发代理
if (delegate) {
[delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
}
}
  • 这个方法和之前的两个方法:

    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)taskdidCompleteWithError:(NSError *)error;
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;

    总共就这3个方法,被转调到AF自定义delegate中。

  • 方法做了什么看注释应该很简单,就不赘述了。
代理14:
//周期性地通知下载进度调用
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
if (self.downloadTaskDidWriteData) {
self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
}

简单说一下这几个参数:
bytesWritten 表示自上次调用该方法后,接收到的数据字节数
totalBytesWritten表示目前已经接收到的数据字节数
totalBytesExpectedToWrite 表示期望收到的文件总字节数,是由Content-Length header提供。如果没有提供,默认是NSURLSessionTransferSizeUnknown。

代理15:
//当下载被取消或者失败后重新恢复下载时调用
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
//交给自定义的Block去调用
if (self.downloadTaskDidResume) {
self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
}
}

官方文档翻译:

函数作用:
告诉代理,下载任务重新开始下载了。

函数讨论:
如果一个正在下载任务被取消或者失败了,你可以请求一个resumeData对象(比如在userInfo字典中通过NSURLSessionDownloadTaskResumeData这个键来获取到resumeData)并使用它来提供足够的信息以重新开始下载任务。
随后,你可以使用resumeData作为downloadTaskWithResumeData:或downloadTaskWithResumeData:completionHandler:的参数。当你调用这些方法时,你将开始一个新的下载任务。一旦你继续下载任务,session会调用它的代理方法URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:其中的downloadTask参数表示的就是新的下载任务,这也意味着下载重新开始了。

总结一下:

  • 其实这个就是用来做断点续传的代理方法。可以在下载失败的时候,拿到我们失败的拼接的部分resumeData,然后用去调用downloadTaskWithResumeData:就会调用到这个代理方法来了。
  • 其中注意:fileOffset这个参数,如果文件缓存策略或者最后文件更新日期阻止重用已经存在的文件内容,那么该值为0。否则,该值表示当前已经下载data的偏移量。
  • 方法中仅仅调用了downloadTaskDidResume自定义Block。

至此NSUrlSesssion的delegate讲完了。大概总结下:

  • 每个代理方法对应一个我们自定义的Block,如果Block被赋值了,那么就调用它。
  • 在这些代理方法里,我们做的处理都是相对于这个sessionManager所有的request的。是公用的处理。
  • 转发了3个代理方法到AF的deleagate中去了,AF中的deleagate是需要对应每个task去私有化处理的

分割图.png

接下来我们来看转发到AF的deleagate,一共3个方法:

AF代理1:
//AF实现的代理!被从urlsession那转发到这

- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{ #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu" //1)强引用self.manager,防止被提前释放;因为self.manager声明为weak,类似Block __strong AFURLSessionManager *manager = self.manager; __block id responseObject = nil; //用来存储一些相关信息,来发送通知用的
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
//存储responseSerializer响应解析对象
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer; //Performance Improvement from #2672 //注意这行代码的用法,感觉写的很Nice...把请求到的数据data传出去,然后就不要这个值了释放内存
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;
} //继续给userinfo填数据
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];
});
});
});
}
#pragma clang diagnostic pop
}

这个方法是NSUrlSession任务完成的代理方法中,主动调用过来的。配合注释,应该代码很容易读,这个方法大概做了以下几件事:

  1. 生成了一个存储这个task相关信息的字典:userInfo,这个字典是用来作为发送任务完成的通知的参数。
  2. 判断了参数error的值,来区分请求成功还是失败。
  3. 如果成功则在一个AF的并行queue中,去做数据解析等后续操作:

    static dispatch_queue_t url_session_manager_processing_queue() {
    static dispatch_queue_t af_url_session_manager_processing_queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT);
    }); return af_url_session_manager_processing_queue;
    }

    注意AF的优化的点,虽然代理回调是串行的(不明白可以见本文最后)。但是数据解析这种费时操作,确是用并行线程来做的。

  4. 然后根据我们一开始设置的responseSerializer来解析data。如果解析成功,调用成功的回调,否则调用失败的回调。
    我们重点来看看返回数据解析这行:

    responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

    我们点进去看看:

    @protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
    
    - (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
    data:(nullable NSData *)data
    error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
    @end

    原来就是这么一个协议方法,各种类型的responseSerializer类,都是遵守这个协议方法,实现了一个把我们请求到的data转换为我们需要的类型的数据的方法。至于各种类型的responseSerializer如何解析数据,我们到代理讲完再来补充。

  5. 这边还做了一个判断,如果自定义了GCD完成组completionGroup和完成队列的话completionQueue,会在加入这个组和在队列中回调Block。否则默认的是AF的创建的组:

    static dispatch_group_t url_session_manager_completion_group() {
    static dispatch_group_t af_url_session_manager_completion_group;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    af_url_session_manager_completion_group = dispatch_group_create();
    }); return af_url_session_manager_completion_group;
    }

    和主队列回调。AF没有用这个GCD组做任何处理,只是提供这个接口,让我们有需求的自行调用处理。如果有对多个任务完成度的监听,可以自行处理。
    而队列的话,如果你不需要回调主线程,可以自己设置一个回调队列。

  6. 回到主线程,发送了任务完成的通知:
    dispatch_async(dispatch_get_main_queue(), ^{
    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
    });

    这个通知这回AF有用到了,在我们对UIKit的扩展中,用到了这个通知。

AF代理2:
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
//拼接数据
[self.mutableData appendData:data];
}

同样被NSUrlSession代理转发到这里,拼接了需要回调的数据。

AF代理3:
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
NSError *fileManagerError = nil;
self.downloadFileURL = nil; //AF代理的自定义Block
if (self.downloadTaskDidFinishDownloading) {
//得到自定义下载路径
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location); if (self.downloadFileURL) {
//把下载路径移动到我们自定义的下载路径
[[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]; //错误发通知
if (fileManagerError) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}

下载成功了被NSUrlSession代理转发到这里,这里有个地方需要注意下:

  • 之前的NSUrlSession代理和这里都移动了文件到下载路径,而NSUrlSession代理的下载路径是所有request公用的下载路径,一旦设置,所有的request都会下载到之前那个路径。
  • 而这个是对应的每个task的,每个task可以设置各自下载路径,还记得AFHttpManager的download方法么

    [manager downloadTaskWithRequest:resquest progress:nil destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
    return path;
    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
    }];

    这个地方return的path就是对应的这个代理方法里的path,我们调用最终会走到这么一个方法:

    - (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
    progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
    destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
    completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
    {
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
    delegate.manager = self;
    delegate.completionHandler = completionHandler; //返回地址的Block
    if (destination) { //有点绕,就是把一个block赋值给我们代理的downloadTaskDidFinishDownloading,这个Block里的内部返回也是调用Block去获取到的,这里面的参数都是AF代理传过去的。
    delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
    //把Block返回的地址返回
    return destination(location, task.response);
    };
    } downloadTask.taskDescription = self.taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:downloadTask]; delegate.downloadProgressBlock = downloadProgressBlock;
    }

    清楚的可以看到地址被赋值给AF的Block了。

至此AF的代理也讲完了,数据或错误信息随着AF代理成功失败回调,回到了用户的手中。

分割图.png

接下来我们来补充之前AFURLResponseSerialization这一块是如何解析数据的:

AFURLResponseSerialization.png

如图所示,AF用来解析数据的一共上述这些方法。第一个实际是一个协议方法,协议方法如下:

@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error; @end

而后面6个类都是遵守这个协议方法,去做数据解析。这地方可以再次感受一下AF的设计模式...接下来我们就来主要看看这些类对这个协议方法的实现:

AFHTTPResponseSerializer:
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
  • 方法调用了一个另外的方法之后,就把data返回来了,我们继续往里看这个方法:
// 判断是不是可接受类型和可接受code,不是则填充error
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
//response是否合法标识
BOOL responseIsValid = YES;
//验证的error
NSError *validationError = nil; //如果存在且是NSHTTPURLResponse
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) { //主要判断自己能接受的数据类型和response的数据类型是否匹配,
//如果有接受数据类型,如果不匹配response,而且响应类型不为空,数据长度不为0
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
!([response MIMEType] == nil && [data length] == 0)) { //进入If块说明解析数据肯定是失败的,这时候要把解析错误信息放到error里。
//如果数据长度大于0,而且有响应url
if ([data length] > 0 && [response URL]) { //错误信息字典,填充一些错误信息
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
} //生成错误
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
} //返回标识
responseIsValid = NO;
} //判断自己可接受的状态吗
//如果和response的状态码不匹配,则进入if块
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
//填写错误信息字典
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy]; if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
} //生成错误
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
//返回标识
responseIsValid = NO;
}
} //给我们传过来的错误指针赋值
if (error && !responseIsValid) {
*error = validationError;
} //返回是否错误标识
return responseIsValid;
}
  • 看注释应该很容易明白这个方法有什么作用。简单来说,这个方法就是来判断返回数据与咱们使用的解析器是否匹配,需要解析的状态码是否匹配。如果错误,则填充错误信息,并且返回NO,否则返回YES,错误信息为nil。
  • 其中里面出现了两个属性值,一个acceptableContentTypes,一个acceptableStatusCodes,两者在初始化的时候有给默认值,我们也可以去自定义,但是如果给acceptableContentTypes定义了不匹配的类型,那么数据仍旧会解析错误。
  • 而AFHTTPResponseSerializer仅仅是调用验证方法,然后就返回了data。
AFJSONResponseSerializer:
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
//先判断是不是可接受类型和可接受code
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
//error为空,或者有错误,去函数里判断。
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
//返回空
return nil;
}
} id responseObject = nil;
NSError *serializationError = nil;
// Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
// See https://github.com/rails/rails/issues/1742 //如果有空格
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
//没空格去json解析
if (data.length > 0 && !isSpace) {
responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
} else {
return nil;
} //判断是否需要移除Null值
if (self.removesKeysWithNullValues && responseObject) {
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
} //拿着json解析的error去填充错误信息
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
} //返回解析结果
return responseObject;
}

注释写的很清楚,大概需要讲一下的是以下几个函数:

//1
AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain))
//2
AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
//3
AFErrorWithUnderlyingError(serializationError, *error);

之前注释已经写清楚了这些函数的作用,首先来看第1个:

//判断是不是我们自己之前生成的错误信息,是的话返回YES
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
//判断错误域名和传过来的域名是否一致,错误code是否一致
if ([error.domain isEqualToString:domain] && error.code == code) {
return YES; }
//如果userInfo的NSUnderlyingErrorKey有值,则在判断一次。
else if (error.userInfo[NSUnderlyingErrorKey]) {
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
} return NO;
}

这里可以注意,我们这里传过去的code和domain两个参数分别为NSURLErrorCannotDecodeContentDataAFURLResponseSerializationErrorDomain,这两个参数是我们之前判断response可接受类型和code时候自己去生成错误的时候填写的。

第二个:

static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
//分数组和字典
if ([JSONObject isKindOfClass:[NSArray class]]) { //生成一个数组,只需要JSONObject.count个,感受到大神写代码的严谨态度了吗...
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
for (id value in (NSArray *)JSONObject) {
//调用自己
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
}
//看我们解析类型是mutable还是非muatable,返回mutableArray或者array
return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray]; } else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
id value = (NSDictionary *)JSONObject[key];
//value空则移除
if (!value || [value isEqual:[NSNull null]]) {
[mutableDictionary removeObjectForKey:key];
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
//如果数组还是去调用自己
mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
}
} return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
} return JSONObject;
}

方法主要还是通过递归的形式实现。比较简单。

第三个:

static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) {
return underlyingError;
} if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
return error;
}
NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
mutableUserInfo[NSUnderlyingErrorKey] = underlyingError; return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}

方法主要是把json解析的错误,赋值给我们需要返回给用户的error上。比较简单,小伙伴们自己看看就好。

至此,AFJSONResponseSerializer就讲完了。
而我们ResponseSerialize还有一些其他的类型解析,大家可以自行去阅读,代码还是很容易读的,在这里就不浪费篇幅去讲了。

分割图.png

在AFURLSessionManager中,有这么一个类:_AFURLSessionTaskSwizzling。这个类大概的作用就是替换掉NSUrlSession中的resumesuspend方法。正常处理原有逻辑的同时,多发送一个通知,以下是我们需要替换的新方法:

//被替换掉的方法,只要有TASK开启或者暂停,都会执行
- (void)af_resume {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_resume]; if (state != NSURLSessionTaskStateRunning) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
}
}
- (void)af_suspend { NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_suspend]; if (state != NSURLSessionTaskStateSuspended) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
}
}

这块知识是关于OC的Runtime:method swizzling的,如果有不清楚的地方,可以看看这里method swizzling--by冰霜或者自行查阅。

+ (void)load {

    if (NSClassFromString(@"NSURLSessionTask")) {

        // 1) 首先构建一个NSURLSession对象session,再通过session构建出一个_NSCFLocalDataTask变量

        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
// 2) 获取到af_resume实现的指针
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class]; // 3) 检查当前class是否实现了resume。如果实现了,继续第4步。
while (class_getInstanceMethod(currentClass, @selector(resume))) { // 4) 获取到当前class的父类(superClass)
Class superClass = [currentClass superclass]; // 5) 获取到当前class对于resume实现的指针
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume))); // 6) 获取到父类对于resume实现的指针
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume))); // 7) 如果当前class对于resume的实现和父类不一样(类似iOS7上的情况),并且当前class的resume实现和af_resume不一样,才进行method swizzling。
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
//执行交换的函数
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
// 8) 设置当前操作的class为其父类class,重复步骤3~8
currentClass = [currentClass superclass];
} [localDataTask cancel];
[session finishTasksAndInvalidate];
}
}

原方法中有大量的英文注释,我把它翻译过来如下:

iOS 7和iOS 8在NSURLSessionTask实现上有些许不同,这使得下面的代码实现略显trick
关于这个问题,大家做了很多Unit Test,足以证明这个方法是可行的
目前我们所知的:

  • NSURLSessionTasks是一组class的统称,如果你仅仅使用提供的API来获取NSURLSessionTask的class,并不一定返回的是你想要的那个(获取NSURLSessionTask的class目的是为了获取其resume方法)
  • 简单地使用[NSURLSessionTask class]并不起作用。你需要新建一个NSURLSession,并根据创建的session再构建出一个NSURLSessionTask对象才行。
  • iOS 7上,localDataTask(下面代码构造出的NSURLSessionDataTask类型的变量,为了获取对应Class)的类型是 NSCFLocalDataTask,NSCFLocalDataTask继承自NSCFLocalSessionTask,NSCFLocalSessionTask继承自__NSCFURLSessionTask。
  • iOS 8上,localDataTask的类型为NSCFLocalDataTask,NSCFLocalDataTask继承自NSCFLocalSessionTask,NSCFLocalSessionTask继承自NSURLSessionTask
  • iOS 7上,NSCFLocalSessionTask和NSCFURLSessionTask是仅有的两个实现了resume和suspend方法的类,另外NSCFLocalSessionTask中的resume和suspend并没有调用其父类(即NSCFURLSessionTask)方法,这也意味着两个类的方法都需要进行method swizzling。
  • iOS 8上,NSURLSessionTask是唯一实现了resume和suspend方法的类。这也意味着其是唯一需要进行method swizzling的类
  • 因为NSURLSessionTask并不是在每个iOS版本中都存在,所以把这些放在此处(即load函数中),比如给一个dummy class添加swizzled方法都会变得很方便,管理起来也方便。

一些假设前提:

  • 目前iOS中resume和suspend的方法实现中并没有调用对应的父类方法。如果日后iOS改变了这种做法,我们还需要重新处理。
  • 没有哪个后台task会重写resume和suspend函数

其余的一部分翻译在注释中,对应那一行代码。大概总结下这个注释:

  • 其实这是被社区大量讨论的一个bug,之前AF因为这个替换方法,会导致偶发性的crash,如果不要这个swizzle则问题不会再出现,但是这样会导致AF中很多UIKit的扩展都不能正常使用。
  • 原来这是因为iOS7和iOS8的NSURLSessionTask的继承链不同导致的,而且在iOS7继承链中会有两个类都实现了resumesuspend方法。而且子类没有调用父类的方法,我们则需要对着两个类都进行方法替换。而iOS8只需要对一个类进行替换。
  • 对着注释看,上述方法代码不难理解,用一个while循环,一级一级去获取父类,如果实现了resume方法,则进行替换。

但是有几个点大家可能会觉得疑惑的,我们先把这个方法调用的替换的函数一块贴出来。

//其引用的交换的函数:
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend)); if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
} if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
}
}
static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));
}

因为有小伙伴问到过,所以我们来分析分析大家可能会觉得疑惑的地方:

  1. 首先可以注意class_getInstanceMethod这个方法,它会获取到当前类继承链逐级往上,第一个实现的该方法。所以说它获取到的方法不能确定是当前类还是父类的。而且这里也没有用dispatch_once_t来保证一个方法只交换一次,那万一这是父类的方法,当前类换一次,父类又换一次,不是等于没交换么?...请注意这行判断:

    // 7) 如果当前class对于resume的实现和父类不一样(类似iOS7上的情况),并且当前class的resume实现和af_resume不一样,才进行method swizzling。
    if (classResumeIMP != superclassResumeIMP && originalAFResumeIMP != classResumeIMP) {
    //执行交换的函数
    [self swizzleResumeAndSuspendMethodForClass:currentClass];
    }

    这个条件就杜绝了这种情况的发生,只有当前类实现了这个方法,才可能进入这个if块。

2.那iOS7两个类都交换了af_resume,那岂不是父类换到子类方法了?...只能说又是没仔细看代码的...注意AF是去向当前类添加af_resume方法,然后去交换当前类的af_resume。所以说根本不会出现这种情况...

AFUrlSessionManager 基本上就这么多内容了。

分割图.png

现在我们回到一开始初始化的这行代码上:

self.operationQueue.maxConcurrentOperationCount = 1;

1)首先我们要明确一个概念,这里的并发数仅仅是回调代理的线程并发数。而不是请求网络的线程并发数。请求网络是由NSUrlSession来做的,它内部维护了一个线程池,用来做网络请求。它调度线程,基于底层的CFSocket去发送请求和接收数据。这些线程是并发的

2)明确了这个概念之后,我们来梳理一下AF3.x的整个流程和线程的关系:

  • 我们一开始初始化sessionManager的时候,一般都是在主线程,(当然不排除有些人喜欢在分线程初始化...)
  • 然后我们调用get或者post等去请求数据,接着会进行request拼接,AF代理的字典映射,progressKVO添加等等,到NSUrlSessionresume之前这些准备工作,仍旧是在主线程中的。
  • 然后我们调用NSUrlSessionresume,接着就跑到NSUrlSession内部去对网络进行数据请求了,在它内部是多线程并发的去请求数据的。
  • 紧接着数据请求完成后,回调回来在我们一开始生成的并发数为1的NSOperationQueue中,这个时候会是多线程串行的回调回来的。(注:不明白的朋友可以看看雷纯峰大神这篇iOS 并发编程之 Operation Queues
  • 然后我们到返回数据解析那一块,我们自己又创建了并发的多线程,去对这些数据进行了各种类型的解析。
  • 最后我们如果有自定义的completionQueue,则在自定义的queue中回调回来,也就是分线程回调回来,否则就是主队列,主线程中回调结束。

3)最后我们来解释解释为什么回调Queue要设置并发数为1:

  • 我认为AF这么做有以下两点原因:
    1)众所周知,AF2.x所有的回调是在一条线程,这条线程是AF的常驻线程,而这一条线程正是AF调度request的思想精髓所在,所以第一个目的就是为了和之前版本保持一致。
    2)因为跟代理相关的一些操作AF都使用了NSLock。所以就算Queue的并发数设置为n,因为多线程回调,锁的等待,导致所提升的程序速度也并不明显。反而多task回调导致的多线程并发,平白浪费了部分性能。
    而设置Queue的并发数为1,(注:这里虽然回调Queue的并发数为1,仍然会有不止一条线程,但是因为是串行回调,所以同一时间,只会有一条线程在操作AFUrlSessionManager的那些方法。)至少回调的事件,是不需要多线程并发的。回调没有了NSLock的等待时间,所以对时间并没有多大的影响。(注:但是还是会有多线程的操作的,因为设置刚开始调起请求的时候,是在主线程的,而回调则是串行分线程。)

当然这仅仅是我个人的看法,如果有不同意见的欢迎交流~

至此我们AF3.X业务层的逻辑,基本上结束了。小伙伴们,看到这你明白了AF做了什么了吗?可能很多朋友要扔鸡蛋了...可能你还是没觉得AF到底有什么用,我用NSUrlSession不也一样,我干嘛要用AF,在这里,我暂时卖个关子,等我们下篇讲完AFSecurityPolicy和部分UIKit的扩展,以及AF2.x的核心类源码实现之后,我们再好好总结。

未完待续...
文/涂耀辉(简书作者)
原文链接:http://www.jianshu.com/p/f32bd79233da
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

AFNetworking到底做了什么?(二)的更多相关文章

  1. AFNetworking到底做了什么

    写在开头: 作为一个iOS开发,也许你不知道NSUrlRequest.不知道NSUrlConnection.也不知道NSURLSession...(说不下去了...怎么会什么都不知道...)但是你一定 ...

  2. 【原】AFNetworking源码阅读(二)

    [原]AFNetworking源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中我们在iOS Example代码中提到了AFHTTPSessionMa ...

  3. volatile关键字到底做了什么?

    话不多说,直接贴代码 class Singleton { private static volatile Singleton instance; private Singleton(){} //双重判 ...

  4. CSS-animations和transitions性能:浏览器到底做了什么?

    CSS animations 和 transitions 的性能:浏览器到底做了什么?(译) 原文地址:http://blogs.adobe.com/webplatform/2014/03/18/cs ...

  5. 一步一步来做WebQQ机器人-(二)(第一次登陆)

    // 预计会有这些步骤,当然某些步骤可能会合并: 验证码 第一次登陆 第二次登陆 保持在线和接收消息 获取好友和群列表 发送消息 变成智能的(*゚∀゚*) webqq的登陆,分为2步,本文主要讲第一次 ...

  6. malloc 函数到底做了什么?

    请看下面的代码. 猜测结果是什么?编译通过吗? #include <stdio.h> #include <stdlib.h> int main() { ; char *ptr ...

  7. new到底做了什么?

    下面是一个实例化自定义的对象,我们将要对他进行分析 //定义构造函数 function A(){ this.b = 1 //在这个对象里增加一个属性 //不可以拥有返回对象的return语句 } va ...

  8. 转Rollback后undo到底做了些什么?

    转自:http://biancheng.dnbcw.info/oracle/309191.html Rollback后undo到底做了些什么? 从概念上讲,undo正好与redo相对.当你对数据执行修 ...

  9. 转 OGG add trandata 到底做了什么

    有的时候我们做OGG的时候add trandata会出现异常. 这里就剖析一下add trandata到底做了什么 GGSCI (yjfora81 as ggs_admin@testdb) 2> ...

随机推荐

  1. webstorm--破解

    2016.2.2 版本的破解方式: 安装以后,打开软件会弹出一个对话框:选择"license server" 输入:http://114.215.133.70:41017 2016 ...

  2. angular_ui-router ——依赖注入

    Angularjs ui-router - 组件: $state / $stateProvider:管理状态定义.当前状态和状态转换.包含触发状态转换的事件和回调函数,异步解决目标状态的任何依赖项,更 ...

  3. sql命令

    oracle 查询所有表中以TICKET开头,TYPE结尾的表,同时不显示末尾为数字的时间表 SELECT * FROM user_col_comments WHERE COLUMN_NAME lik ...

  4. python 学习笔记十九 django深入学习四 cookie,session

    缓存 一个动态网站的基本权衡点就是,它是动态的. 每次用户请求一个页面,Web服务器将进行所有涵盖数据库查询到模版渲染到业务逻辑的请求,用来创建浏览者需要的页面.当程序访问量大时,耗时必然会更加明显, ...

  5. winform 移动窗体,和窗体阴影(引用)

    无边框窗体移动://窗体移动API [DllImport("user32.dll")] public static extern bool ReleaseCapture(); [D ...

  6. 按年、季度、月分组&&计算日期和时间的函数

    Mysql 按年.季度.月分组 按月度分组: select DATE_FORMAT(i.created_at, '%Y-%m月')...................GROUP BY DATE_FO ...

  7. 公钥私钥和RSA算法

    1, RSA算法原理(一) http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html 2, RSA算法原理(二) http: ...

  8. python 初级1

    List:Python内置的一种数据类型是列表:list.list是一种有序的集合,可以随时添加和删除其中的元素. 构造list非常简单,按照上面的代码,直接用 [ ] 把list的所有元素都括起来, ...

  9. qt creator 使用Ui文件的问题

    一.显式地调用uic.exe 如何将UI文件生成头文件 1,将设计的UI文件拷贝到uic.exe的目录下Qt\Qt5.3.2\5.3\mingw482_32\bin. 2, 打开windows的CMD ...

  10. GOLANG 基本数据类型 整型

    基本数据类型-整型 种类     有符号(负号)      int8 int16 int32 int64 无符号(无符号) uint8 uint16 uint32 uint64 架构特定(取决于系统位 ...