【原】AFNetworking源码阅读(四)

本文转载请注明出处 —— polobymulberry-博客园

1. 前言


上一篇还遗留了很多问题,包括AFURLSessionManagerTaskDelegate类所实现的NSURLSession相关的代理方法,甚至连dataTask、uploadTask、downloadTask这几个基本概念也没说。这一篇就是为了集中消灭这些遗留问题。

2. AFURLSessionManagerTaskDelegate的代理方法


此处实现的仍然是NSURLSession相关的代理方法,因为上一篇中已经详细介绍过了,所以对应的相关方法介绍就不赘述,直接介绍方法实现。

2.1 NSURLSessionTaskDelegate

2.1.1 - URLSession:task:didCompleteWithError:

该函数在AFURLSessionManager中的- URLSession:task:didCompleteWithError:被调用

函数实现:

  1. - (void)URLSession:(__unused NSURLSession *)session
  2. task:(NSURLSessionTask *)task
  3. didCompleteWithError:(NSError *)error
  4. {
  5. // 保存clang诊断的上下文,类似OpenGL状态机,和后面的pop配对使用
  6. #pragma clang diagnostic push
  7. // 使用?:符号,注意x ? x : y == x ?: y,之前博客中要是有理解错误,以此为准
  8. #pragma clang diagnostic ignored "-Wgnu"
  9. __strong AFURLSessionManager *manager = self.manager;
  10.  
  11. __block id responseObject = nil;
  12.  
  13. // 因为NSNotification这个类中本身有userInfo属性,可作为响应函数的参数
  14. // 不过我在AFNetworking源码中还未发现使用userInfo作为参数的做法,可能需要用户自己实现
  15.  
  16. /**
  1. * userInfo中的key值例举如下:
  2. * AFNetworkingTaskDidCompleteResponseDataKey session 存储task获取到的原始response数据,与序列化后的response有所不同
  3. * AFNetworkingTaskDidCompleteSerializedResponseKey 存储经过序列化(serialized)后的response
  4. * AFNetworkingTaskDidCompleteResponseSerializerKey 保存序列化response的序列化器(serializer)
  5. * AFNetworkingTaskDidCompleteAssetPathKey 存储下载任务后,数据文件存放在磁盘上的位置
  6. * AFNetworkingTaskDidCompleteErrorKey 错误信息
  7. */
  8. __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
  9. // serializer
  10. userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
  11.  
  12. //具体可以查看#issue 2672。这里主要是针对大文件的时候,性能提升会很明显
  13. NSData *data = nil;
  14. if (self.mutableData) { // 要先判断是否为nil
  15. data = [self.mutableData copy];
  16. //此处不再需要mutableData了
  17. self.mutableData = nil;
  18. }
  19.  
  20. if (self.downloadFileURL) {
  21. userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
  22. } else if (data) {
  23. userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
  24. }
  25. // 如果task出错了,处理error信息
  26. // 所以对应的观察者在处理error的时候,比如可以先判断userInfo[AFNetworkingTaskDidCompleteErrorKey]是否有值,有值的话,就说明是要处理error
  27. if (error) {
  28. userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
  29. // 这里用group方式来运行task完成方法,表示当前所有的task任务完成,才会通知执行其他操作
  30. // 如果没有实现自定义的completionGroup和completionQueue,那么就使用AFNetworking提供的私有的dispatch_group_t和提供的dispatch_get_main_queue内容
  31. dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
  32. if (self.completionHandler) {
  33. self.completionHandler(task.response, responseObject, error);
  34. }
  35.  
  36. dispatch_async(dispatch_get_main_queue(), ^{
  37. [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
  38. });
  39. });
  40. } else {
  41. dispatch_async(url_session_manager_processing_queue(), ^{
  42. NSError *serializationError = nil;
  43. // 根据对应的task和data将response data解析成可用的数据格式,比如JSON serializer就将data解析成JSON格式
  44. responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
  45. // 注意如果有downloadFileURL,意味着data存放在了磁盘上了,所以此处responseObject保存的是data存放位置,供后面completionHandler处理。没有downloadFileURL,就直接使用内存中的解析后的data数据
  46. if (self.downloadFileURL) {
  47. responseObject = self.downloadFileURL;
  48. }
  49.  
  50. if (responseObject) {
  51. userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
  52. }
  53. // 序列化的时候出现错误
  54. if (serializationError) {
  55. userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
  56. }
  57. // 同上面的代码
  58. dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
  59. if (self.completionHandler) {
  60. self.completionHandler(task.response, responseObject, serializationError);
  61. }
  62.  
  63. dispatch_async(dispatch_get_main_queue(), ^{
  64. [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
  65. });
  66. });
  67. });
  68. }
  69. #pragma clang diagnostic pop
  70. }

2.2 NSURLSessionDataDelegate

2.2.1 - URLSession:dataTask:didReceiveData:

该函数在AFURLSessionManager中的- URLSession:dataTask:didReceiveData:被调用

函数实现:

  1. - (void)URLSession:(__unused NSURLSession *)session
  2. dataTask:(__unused NSURLSessionDataTask *)dataTask
  3. didReceiveData:(NSData *)data
  4. {
  5. // 将每次获得的新数据附在mutableData上,来组成最终获得的所有数据
  6. [self.mutableData appendData:data];
  7. }

2.3 NSURLSessionDownloadDelegate

2.3.1 - URLSession:downloadTask:didFinishDownloadingToURL:(必须实现)

该函数在AFURLSessionManager中的- URLSession:downloadTask:didFinishDownloadingToURL:被调用
函数实现:

和AFURLSessionManager中的实现类似,这里就不赘述了。

3. 进一步讨论session task


首先简单介绍下session task,以下语句引用自从 NSURLConnection 到 NSURLSession


NSURLsessionTask 是一个抽象类,其下有 3 个实体子类可以直接使用:NSURLSessionDataTaskNSURLSessionUploadTaskNSURLSessionDownloadTask。这 3 个子类封装了现代程序三个最基本的网络任务:获取数据,比如 JSON 或者 XML,上传文件和下载文件。

当一个 NSURLSessionDataTask 完成时,它会带有相关联的数据,而一个 NSURLSessionDownloadTask 任务结束时,它会带回已下载文件的一个临时的文件路径(还记得前面的location吧)。因为一般来说,服务端对于一个上传任务的响应也会有相关数据返回,所以NSURLSessionUploadTask 继承自 NSURLSessionDataTask


之前讨论dataTask比较多,对于uploadTask和downloadTask提及较少。比如我们之前只说了- [AFURLSessionManager dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:]。其实还有类似的uploadTaskWithRequest:和downloadTaskWithRequest:等方法。

不知道大家看到这里会不会跟我一样有疑问——已经有了dataTask了,为什么还要实现一个uploadTask?我们从两者提供的对应task 生成的方法能看出一点端倪。比如使用dataTask来进行上传任务的时候,需要指定HTTPMethod为POST或PUT,并且提供的数据(NSData)得赋值给request.HTTPBody。而使用uploadTask来进行上传任务的时候,只需要使用- uploadTaskWithRequest:fromData:或- uploadTaskWithRequest:fromFile:之类的方法,其中参数的话只需要根提供数据(NSData)或者数据的磁盘位置(NSURL*fileURL)就可以构造出一个上传的session task了,简化了操作。

至于uploadTaskWithRequest:和downloadTaskWithRequest:等方法实现上本质和dataTaskWithRequest:并没有多大区别,这里对于相同的地方就不赘述了,主要提几点不同的地方,而这几点不同的地方根本在于系统提供了不同session task生成方法

1. 系统提供的uploadTask构建方法:

  • uploadTaskWithRequest:fromFile: 根据fileURL创建request

对应AFNetworking中的uploadTaskWithRequest:fromFile:progress:completionHandler:方法,关于这个方法,里面使用到了attemptsToRecreateUploadTasksForBackgroundSessions变量,这个是用于创建后台task时使用的。因为在iOS7中,有时候创建后台task会失败,Apple建议如果创建失败了,就重新尝试创建。此处尝试的次数最大为AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask,默认为3。至于其中使用到的addDelegateForUploadTask:地实现基本同addDelegateForDataTask:实现。

详见源码。

  • uploadTaskWithRequest:fromData: 根据需要上传的NSData创建request

对应AFNetworking中的uploadTaskWithRequest:fromData:progress:completionHandler:方法。

详见源码。

  • uploadTaskWithStreamedRequest: 使用该函数必须要实现URLSession:task:needNewBodyStream:来给上传任务提供数据

对应AFNetworking中的uploadTaskWithRequest:fromData:progress:completionHandler:方法。

详见源码。

2. 系统提供的downloadTask构建方法:

  • downloadTaskWithRequest: 不赘述

对应AFNetworking中的downloadTaskWithRequest:progress:destination:completionHandler:方法,注意此处多了一个destination。destination是一个block:

  1. (nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination

该block表示下载后的文件最后如何放置,返回的是一个NSURL*变量。具体使用请看addDelegateForDownloadTask:

  1. - (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
  2. progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
  3. destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
  4. completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
  5. {
  6. // ......
  7.  
  8. if (destination) {
  9. // 会调用setDownloadTaskDidFinishDownloadingBlock:方法,生成最终下载文件放置位置
  10. delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
  11. return destination(location, task.response);
  12. };
  13. }
  14. // ......
  15. }
  • downloadTaskWithResumeData: 用于断点续传,resumeData就是上一篇文章中提到的用于提供断点续传的信息。

对应AFNetworking中的downloadTaskWithResumeData:progress:destination:completionHandler:方法。

4. _AFURLSessionTaskSwizzling


这个类在#issues 1477上reopen了多次,讨论还是很激烈的。讨论的起由是app会莫名crash,主要原因是AFNetworking对NSURLSessionTask中的state进行了KVO操作。一开始人们removeObserver这个state,但是会造成AFNetworkActivityIndicatorManager功能(其中会观察state)削弱。另外后来iOS8上也出现了同样crash现象,貌似iOS7和iOS8在NSURLSessionTask有些不同。最后还是有个大神用swizzling方法才解决了这个问题。

还记得【原】AFNetworking源码阅读(三)中我们提到了如果想使用AFNetworkingTaskDidResumeNotification来通知各种UI控件当前网络任务状态为resume,那么就得调用taskDidResume:函数,而想要调用taskDidResume:函数就得调用af_resume函数。之前我们提到过,af_resume和系统的resume进行了method swizzling。所以调用af_resume其实就是调用resume。

不过你有没发现除了后面Test中的方法出现了_AFURLSessionTaskSwizzling,其他地方都没出现该类的使用,那method swizzling是在哪初始化的的呢,换句话说,af_resume和resume是在哪调换的?这个问题我想了好久,最后才明白,都是自己学艺不精啊。下面补充一个知识点:


知识点:load的调用时机

load方法会在加载类的时候就被调用,也就是iOS应用启动的时候就会加载所有的类,就会调用每个类的+load方法。

而我们的_AFURLSessionTaskSwizzling重写了load方法,并且在其中调用了swizzleResumeAndSuspendMethodForClass:来进行method swizzling。下面我们先看看swizzleResumeAndSuspendMethodForClass:这个方法:

  1. + (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
  2. // 因为af_resume和af_suspend都是类的实例方法,所以使用class_getInstanceMethod获取这两个方法
  3. Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
  4. Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
  5.  
  6. // 给theClass添加一个名为af_resume的方法,使用@selector(af_resume)获取方法名,使用afResumeMethod作为方法实现
  7. if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
  8. // 交换resume和af_resume的方法实现
  9. af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
  10. }
  11. // 同上
  12. if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
  13. af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
  14. }
  15. }

上述方法调用了大量私有的方法,下面一一解释:

  1. // 根据两个方法名称交换两个方法,内部实现是先根据函数名获取到对应方法实现
  2. // 再调用method_exchangeImplementations交换两个方法
  3. static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
  4. Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
  5. Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
  6. method_exchangeImplementations(originalMethod, swizzledMethod);
  7. }
  8.  
  9. // 给theClass添加名为selector,对应实现为method的方法
  10. static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
  11. // 内部实现使用的是class_addMethod方法,注意method_getTypeEncoding是为了获得该方法的参数和返回类型
  12. return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));
  13. }
  1. - (NSURLSessionTaskState)state {
  2. NSAssert(NO, @"State method should never be called in the actual dummy class");
  3. // 初始状态是NSURLSessionTaskStateCanceling;
  4. return NSURLSessionTaskStateCanceling;
  5. }
  6.  
  7. - (void)af_resume {
  8. NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
  9. NSURLSessionTaskState state = [self state];
  10. [self af_resume]; // 因为经过method swizzling后,此处的af_resume其实就是之前的resume,所以此处调用af_resume就是调用系统的resume。但是在程序中我们还是得使用resume,因为其实际调用的是af_resume
  11. // 如果之前是其他状态,就变回resume状态,此处会通知调用taskDidResume
  12. if (state != NSURLSessionTaskStateRunning) {
  13. [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
  14. }
  15. }
  16.  
  17. // 同上
  18. - (void)af_suspend {
  19. NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
  20. NSURLSessionTaskState state = [self state];
  21. [self af_suspend];
  22.  
  23. if (state != NSURLSessionTaskStateSuspended) {
  24. [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
  25. }
  26. }

解释完上面的函数后,最终回到我们的load函数:

  1. + (void)load {
  2. /**
  3. WARNING: 高能预警
  4. https://github.com/AFNetworking/AFNetworking/pull/2702
  5. */
  6. // 担心以后iOS中不存在NSURLSessionTask
  7. if (NSClassFromString(@"NSURLSessionTask")) {
  8. /**
  9. iOS 7和iOS 8在NSURLSessionTask实现上有些许不同,这使得下面的代码实现略显trick
  10. 关于这个问题,大家做了很多Unit Test,足以证明这个方法是可行的
  11. 目前我们所知的:
  12. - NSURLSessionTasks是一组class的统称,如果你仅仅使用提供的API来获取NSURLSessionTask的class,并不一定返回的是你想要的那个(获取NSURLSessionTask的class目的是为了获取其resume方法)
  13. - 简单地使用[NSURLSessionTask class]并不起作用。你需要新建一个NSURLSession,并根据创建的session再构建出一个NSURLSessionTask对象才行。
  14. - iOS 7上,localDataTask(下面代码构造出的NSURLSessionDataTask类型的变量,为了获取对应Class)的类型是 __NSCFLocalDataTask,__NSCFLocalDataTask继承自__NSCFLocalSessionTask,__NSCFLocalSessionTask继承自__NSCFURLSessionTask。
  15. - iOS 8上,localDataTask的类型为__NSCFLocalDataTask,__NSCFLocalDataTask继承自__NSCFLocalSessionTask,__NSCFLocalSessionTask继承自NSURLSessionTask
  16. - iOS 7上,__NSCFLocalSessionTask和__NSCFURLSessionTask是仅有的两个实现了resume和suspend方法的类,另外__NSCFLocalSessionTask中的resume和suspend并没有调用其父类(即__NSCFURLSessionTask)方法,这也意味着两个类的方法都需要进行method swizzling。
  17. - iOS 8上,NSURLSessionTask是唯一实现了resume和suspend方法的类。这也意味着其是唯一需要进行method swizzling的类
  18. - 因为NSURLSessionTask并不是在每个iOS版本中都存在,所以把这些放在此处(即load函数中),比如给一个dummy class添加swizzled方法都会变得很方便,管理起来也方便。
  19.  
  20. 一些假设前提:
  21. - 目前iOS中resume和suspend的方法实现中并没有调用对应的父类方法。如果日后iOS改变了这种做法,我们还需要重新处理
  22. - 没有哪个后台task会重写resume和suspend函数
  23.  
  24. */
  25. // 1) 首先构建一个NSURLSession对象session,再通过session构建出一个_NSCFLocalDataTask变量
  26. NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
  27. NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
  28. #pragma GCC diagnostic push
  29. #pragma GCC diagnostic ignored "-Wnonnull"
  30. NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
  31. #pragma clang diagnostic pop
  32. // 2) 获取到af_resume实现的指针
  33. IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
  34. Class currentClass = [localDataTask class];
  35. // 3) 检查当前class是否实现了resume。如果实现了,继续第4步。
  36. while (class_getInstanceMethod(currentClass, @selector(resume))) {
  37. // 4) 获取到当前class的父类(superClass)
  38. Class superClass = [currentClass superclass];
  39. // 5) 获取到当前class对于resume实现的指针
  40. IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
  41. // 6) 获取到父类对于resume实现的指针
  42. IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
  43. // 7) 如果当前class对于resume的实现和父类不一样(类似iOS7上的情况),并且当前class的resume实现和af_resume不一样,才进行method swizzling。
  44. if (classResumeIMP != superclassResumeIMP &&
  45. originalAFResumeIMP != classResumeIMP) {
  46. [self swizzleResumeAndSuspendMethodForClass:currentClass];
  47. }
  48. // 8) 设置当前操作的class为其父类class,重复步骤3~8
  49. currentClass = [currentClass superclass];
  50. }
  51.  
  52. [localDataTask cancel];
  53. [session finishTasksAndInvalidate];
  54. }
  55. }

5. AFURLSessionManager剩余部分-NSSecureCoding和NSCopying


5.1 NSSecureCoding

关于NSSecureCoding的讲解请参考使用NSSecureCoding协议进行编解码

因为要支持secure coding,所以要在supportsSecureCoding返回YES。

AFURLSessionManager保存的信息是其NSURLSessionConfiguration变量,然后根据获取到的configuration构建出AFURLSessionManager对象,节省了存储空间。

  1. + (BOOL)supportsSecureCoding {
  2. return YES;
  3. }
  4.  
  5. - (instancetype)initWithCoder:(NSCoder *)decoder {
  6. NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"];
  7.  
  8. self = [self initWithSessionConfiguration:configuration];
  9. if (!self) {
  10. return nil;
  11. }
  12.  
  13. return self;
  14. }

5.2 NSCopying

没啥好说的,就是先构建一个AFURLSessionManager空间,并使用原先session的configuration来初始化空间内容。

  1. - (instancetype)copyWithZone:(NSZone *)zone {
  2. return [[[self class] allocWithZone:zone] initWithSessionConfiguration:self.session.configuration];
  3. }

讲到这,基本上AFURLSessionManager这个文件的内容已经东一点西一点讲完了。下面,我们再来跳到AFHTTPSessionManager这个文件中,看看还有哪些内容没有讲完。

6. AFHTTPSessionManager剩余部分


6.1 - [AFHTTPSessionManager POST:parameters:constructingBodyWithBlock:progress:success:failure:]

这个带constructingBody的POST方法主要是为了解决Multipart协议的问题。


知识点:Multipart协议介绍 —— 详见HTTP协议之multipart/form-data请求分析,或者你看这篇文章https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2(一定要先看完)

这里简述一下:Multipart是HTTP协议为web表单新增的上传文件的协议,协议文档是rfc1867,它基于HTTP的POST方法,数据同样是放在body上,跟普通POST方法的区别是数据不是key=value形式,key=value形式难以表示文件实体,为此Multipart协议添加了分隔符(即boundary的概念),有自己的格式结构,举个例子:

  1. --${bound} // 该bound表示pdf的文件名
  2. Content-Disposition: form-data; name="Filename"
  3.  
  4. HTTP.pdf
  5. --${bound} // 该bound表示pdf的文件内容
  6. Content-Disposition: form-data; name="file000"; filename="HTTP协议详解.pdf"
  7. Content-Type: application/octet-stream
  8.  
  9. %PDF-1.5
  10. file content
  11. %%EOF
  12.  
  13. --${bound} // 该bound表示字符串
  14. Content-Disposition: form-data; name="Upload"
  15.  
  16. Submit Query
  17. --${bound}—// 表示body结束了

举例:比如上面那个例子,我们如果想使用multipart形式调用,应该使用怎样的调用方法?

先说结论:

  1. AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
  2. [manager POST:@"postURLString" parameters:@{@"Filename":@"HTTP.pdf"} constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
  3. [formData appendPartWithFileData:[pdf文件具体内容(NSData *)]
  4. name:@"file000"
  5. fileName:@"HTTP协议详解.pdf"
  6. mimeType:@"application/octet-stream"];
  7. [formData appendPartWithFormData:[@"Submit Query" dataUsingEncoding:NSUTF8StringEncoding]
  8. name:@"Upload"];
  9. } progress:nil success:nil failure:nil];

有些函数,比如appendPartWithFileData:和appendPartWithFormData:这些函数,大家对照上面的例子,也大概能猜出来大概用途了,具体实现后面会详解。

而此处带constructingBodyWithBlock的POST方法与- [AFHTTPSessionManager POST:parameters:progress:success:failure:]明显的区别在于构建request的时候,使用的是multipartFormRequestWithMethod:以及构建NSURLSessionDataTask的时候使用的是uploadTaskWithStreamedRequest:。因为uploadTaskWithStreamedRequest:函数在上面已经提到过了。这里就主要说一下multipartFormRequestWithMethod:实现。

multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:除了需要使用普通的request构造函数requestWithMethod:URLString:parameters:error:来构造request,还需要根据multipart独有的属性来修饰这个request,其中最关键的就是要构造http body部分。下面我挑出了其中比较关键的代码进行分析:

  1. // 使用initWithURLRequest:stringEncoding:来初始化一个AFStreamingMultipartFormData变量
  2. // 每个AFStreamMultipartFormData其实都是对应一个上面举的那个例子,主要是为了构建bodyStream
  3. __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
  4. // 处理parameters,比如上面的@"Filename":"HTTP.pdf",首先构建一个AFQueryStringPair,其中field为"Filename",value为"HTTP.pdf"

// 然后会根据对应value的类型,构建出一个NSData变量。比如此处的value是一个NSString,所以调用//data = [[pair.value description] dataUsingEncoding:self.stringEncoding];将NSString->NSData

  1. if (parameters) {
  2. for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
  3. NSData *data = nil;
  4. if ([pair.value isKindOfClass:[NSData class]]) {
  5. data = pair.value;
  6. } else if ([pair.value isEqual:[NSNull null]]) {
  7. data = [NSData data];
  8. } else {
  9. data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
  10. }
  11.  
  12. if (data) {
  13. // bodyStream构造最主要的部分就在这了(虽然后面requestByFinalizingMultipartFormData函数还会稍微处理一下)
  1. // 根据data和name构建Request的header和body,后面详解
  2. [formData appendPartWithFormData:data name:[pair.field description]];
  3. }
  4. }
  5. }
  6. // 参考上面的例子,其实还是往formData中添加数据
  7. if (block) {
  8. block(formData);
  9. }
  10. // 做最终的处理,比如设置一下MultipartRequest的bodyStream或者其特有的content-type等等,后面也会详解
  11. return [formData requestByFinalizingMultipartFormData];

至于AFStreamMultipartFormData类,appendPartWithFormData:和requestByFinalizingMultipartFormData等等函数,我大概看了下内容还是比较多的,准备在下一篇中介绍AFURLRequestSerialization时详细介绍。此处我们只需要知道这里构建了一个Multipart Request给uploadTask构造时使用。

6.2 NSSecureCoding

  1. // 对baseURL,session.configuration,requestSerializer,responseSerializer,securityPolicy进行编码
  2. - (void)encodeWithCoder:(NSCoder *)coder {
  3. // AFHTTPSessionManager的父类为AFURLSessionManager,所以先调用父类方法
  4. [super encodeWithCoder:coder];
  5. // 因为configuration是一个对象,所以要考虑是否实现了NSCoding
  6. [coder encodeObject:self.baseURL forKey:NSStringFromSelector(@selector(baseURL))];
  7. if ([self.session.configuration conformsToProtocol:@protocol(NSCoding)]) {
  8. [coder encodeObject:self.session.configuration forKey:@"sessionConfiguration"];
  9. } else {
  10. [coder encodeObject:self.session.configuration.identifier forKey:@"identifier"];
  11. }
  12. [coder encodeObject:self.requestSerializer forKey:NSStringFromSelector(@selector(requestSerializer))];
  13. [coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))];
  14. [coder encodeObject:self.securityPolicy forKey:NSStringFromSelector(@selector(securityPolicy))];
  15. }

对于initWithCoder:就不赘述了。

6.3 NSCopying

  1. // 深拷贝,递归地拷贝下去
  2. - (instancetype)copyWithZone:(NSZone *)zone {
  3. AFHTTPSessionManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL sessionConfiguration:self.session.configuration];
  4.  
  5. HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone];
  6. HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone];
  7. HTTPClient.securityPolicy = [self.securityPolicy copyWithZone:zone];
  8. return HTTPClient;
  9. }

写NSSecureCoding和NSCopying的目的,不是因为这两个函数有什么难度在里面,而是为了时刻提醒自己,还要记得这两个协议,学会使用它们。后面除非这两个协议有特殊处理,就不讨论了。

7. AFURLSessionManagerTests和AFHTTPSessionManagerTests


主要还是利用https://httpbin.org/提供的各种借口进行测试,比如重定向使用/redirect/1测试,状态码返回204使用/status/204测试等等。本文不想过多介绍httpbin网站内容,大家感兴趣,自行研究。另外test中有很多类似函数使用的例子可以作为参考,比如POST等等函数的使用方法,所以还是值得看看的,这里我就不费口舌了。

8. 总结


这一篇比较零散,主要是给AFURLSessionManager和AFHTTPSessionManager两个文件擦屁股的。所以有些问题请结合之前的文章一起来看。下面几篇就比较单纯了,比如request序列化,response序列化、安全策略和网络状态管理这几个模块可以按独立部分来学习。

9. 参考文章


【原】AFNetworking源码阅读(四)的更多相关文章

  1. 【原】AFNetworking源码阅读(六)

    [原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...

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

    [原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...

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

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

  4. 【原】AFNetworking源码阅读(一)

    [原]AFNetworking源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 AFNetworking版本:3.0.4 由于我平常并没有经常使用AFNetw ...

  5. 【原】AFNetworking源码阅读(三)

    [原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data tas ...

  6. 39 网络相关函数(七)——live555源码阅读(四)网络

    39 网络相关函数(七)——live555源码阅读(四)网络 39 网络相关函数(七)——live555源码阅读(四)网络 简介 14)readSocket从套接口读取数据 recv/recvfrom ...

  7. 40 网络相关函数(八)——live555源码阅读(四)网络

    40 网络相关函数(八)——live555源码阅读(四)网络 40 网络相关函数(八)——live555源码阅读(四)网络 简介 15)writeSocket向套接口写数据 TTL的概念 函数send ...

  8. 38 网络相关函数(六)——live555源码阅读(四)网络

    38 网络相关函数(六)——live555源码阅读(四)网络 38 网络相关函数(六)——live555源码阅读(四)网络 简介 12)makeSocketNonBlocking和makeSocket ...

  9. 37 网络相关函数(五)——live555源码阅读(四)网络

    37 网络相关函数(五)——live555源码阅读(四)网络 37 网络相关函数(五)——live555源码阅读(四)网络 简介 10)MAKE_SOCKADDR_IN构建sockaddr_in结构体 ...

随机推荐

  1. react组件的生命周期

    写在前面: 阅读了多遍文章之后,自己总结了一个.一遍加强记忆,和日后回顾. 一.实例化(初始化) var Button = React.createClass({ getInitialState: f ...

  2. gentoo 安装

    加载完光驱后 1进行ping命令查看网络是否通畅 2设置硬盘的标识为GPT(主要用于64位且启动模式为UEFI,还有一个是MBR,主要用于32位且启动模式为bois) parted -a optima ...

  3. Web Api 入门实战 (快速入门+工具使用+不依赖IIS)

    平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html 屁话我也就不多说了,什么简介的也省了,直接简单概括+demo ...

  4. .NET Core的日志[4]:将日志写入EventLog

    面向Windows的编程人员应该不会对Event Log感到陌生,以至于很多人提到日志,首先想到的就是EventLog.EventLog不仅仅记录了Windows系统自身针对各种事件的日志,我们的应用 ...

  5. 6. ModelDriven拦截器、Preparable 拦截器

    1. 问题 Struts2 的 Action 我们将它定义为一个控制器,但是由于在 Action 中也可以来编写一些业务逻辑,也有人会在 Action 输入业务逻辑层. 但是在企业开发中,我们一般会将 ...

  6. Win.ini和注册表的读取写入

    最近在做打包的工作,应用程序的配置信息可以放在注册表文件中,但是在以前的16位操作系统下,配置信息放在Win.ini文件中.下面介绍一下Win.ini文件的读写方法和注册表的编程. 先介绍下Win.i ...

  7. C#中将DataTable导出为HTML的方法

    今天我要向大家分享一种将DataTable导出为到HTML格式的方法.有时我们需要HTML格式的输出数据, 以下代码就可以帮助我们达到目的,. 首先,我们要绑定DataTable和 DataGridV ...

  8. PHP 获取 特定时间范围 类

    目录  前序 用途 功能及事项 使用方法 代码及注释 前序: 总体来说,我更应该是一个 android 移动开发者,而不是一个 phper,如果说只做移动端的 APP ,我也不会学这么多,这 2年来, ...

  9. AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager

    让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...

  10. ZKWeb网页框架1.4正式发布

    本次更新的内容有 添加更快的批量操作函数 添加IDatabaseContext.FastBatchSave 添加IDatabaseContext.FastBatchDelete 注意这些函数不会触发注 ...