【原】AFNetworking源码阅读(四)
【原】AFNetworking源码阅读(四)
本文转载请注明出处 —— polobymulberry-博客园
1. 前言
上一篇还遗留了很多问题,包括AFURLSessionManagerTaskDelegate类所实现的NSURLSession相关的代理方法,甚至连dataTask、uploadTask、downloadTask这几个基本概念也没说。这一篇就是为了集中消灭这些遗留问题。
2. AFURLSessionManagerTaskDelegate的代理方法
此处实现的仍然是NSURLSession相关的代理方法,因为上一篇中已经详细介绍过了,所以对应的相关方法介绍就不赘述,直接介绍方法实现。
2.1 NSURLSessionTaskDelegate
2.1.1 - URLSession:task:didCompleteWithError:
该函数在AFURLSessionManager中的- URLSession:task:didCompleteWithError:被调用
函数实现:
- - (void)URLSession:(__unused NSURLSession *)session
- task:(NSURLSessionTask *)task
- didCompleteWithError:(NSError *)error
- {
- // 保存clang诊断的上下文,类似OpenGL状态机,和后面的pop配对使用
- #pragma clang diagnostic push
- // 使用?:符号,注意x ? x : y == x ?: y,之前博客中要是有理解错误,以此为准
- #pragma clang diagnostic ignored "-Wgnu"
- __strong AFURLSessionManager *manager = self.manager;
- __block id responseObject = nil;
- // 因为NSNotification这个类中本身有userInfo属性,可作为响应函数的参数
- // 不过我在AFNetworking源码中还未发现使用userInfo作为参数的做法,可能需要用户自己实现
- /**
- * userInfo中的key值例举如下:
- * AFNetworkingTaskDidCompleteResponseDataKey session 存储task获取到的原始response数据,与序列化后的response有所不同
- * AFNetworkingTaskDidCompleteSerializedResponseKey 存储经过序列化(serialized)后的response
- * AFNetworkingTaskDidCompleteResponseSerializerKey 保存序列化response的序列化器(serializer)
- * AFNetworkingTaskDidCompleteAssetPathKey 存储下载任务后,数据文件存放在磁盘上的位置
- * AFNetworkingTaskDidCompleteErrorKey 错误信息
- */
- __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
- // serializer
- userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
- //具体可以查看#issue 2672。这里主要是针对大文件的时候,性能提升会很明显
- NSData *data = nil;
- if (self.mutableData) { // 要先判断是否为nil
- data = [self.mutableData copy];
- //此处不再需要mutableData了
- self.mutableData = nil;
- }
- if (self.downloadFileURL) {
- userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
- } else if (data) {
- userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
- }
- // 如果task出错了,处理error信息
- // 所以对应的观察者在处理error的时候,比如可以先判断userInfo[AFNetworkingTaskDidCompleteErrorKey]是否有值,有值的话,就说明是要处理error
- if (error) {
- userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
- // 这里用group方式来运行task完成方法,表示当前所有的task任务完成,才会通知执行其他操作
- // 如果没有实现自定义的completionGroup和completionQueue,那么就使用AFNetworking提供的私有的dispatch_group_t和提供的dispatch_get_main_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 {
- dispatch_async(url_session_manager_processing_queue(), ^{
- NSError *serializationError = nil;
- // 根据对应的task和data将response data解析成可用的数据格式,比如JSON serializer就将data解析成JSON格式
- responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
- // 注意如果有downloadFileURL,意味着data存放在了磁盘上了,所以此处responseObject保存的是data存放位置,供后面completionHandler处理。没有downloadFileURL,就直接使用内存中的解析后的data数据
- if (self.downloadFileURL) {
- responseObject = self.downloadFileURL;
- }
- 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
- }
2.2 NSURLSessionDataDelegate
2.2.1 - URLSession:dataTask:didReceiveData:
该函数在AFURLSessionManager中的- URLSession:dataTask:didReceiveData:被调用
函数实现:
- - (void)URLSession:(__unused NSURLSession *)session
- dataTask:(__unused NSURLSessionDataTask *)dataTask
- didReceiveData:(NSData *)data
- {
- // 将每次获得的新数据附在mutableData上,来组成最终获得的所有数据
- [self.mutableData appendData:data];
- }
2.3 NSURLSessionDownloadDelegate
2.3.1 - URLSession:downloadTask:didFinishDownloadingToURL:(必须实现)
该函数在AFURLSessionManager中的- URLSession:downloadTask:didFinishDownloadingToURL:被调用
函数实现:
和AFURLSessionManager中的实现类似,这里就不赘述了。
3. 进一步讨论session task
首先简单介绍下session task,以下语句引用自从 NSURLConnection 到 NSURLSession
NSURLsessionTask
是一个抽象类,其下有 3 个实体子类可以直接使用:NSURLSessionDataTask
、NSURLSessionUploadTask
、NSURLSessionDownloadTask
。这 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:
- (nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
该block表示下载后的文件最后如何放置,返回的是一个NSURL*变量。具体使用请看addDelegateForDownloadTask:
- - (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
- {
- // ......
- if (destination) {
- // 会调用setDownloadTaskDidFinishDownloadingBlock:方法,生成最终下载文件放置位置
- delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
- return destination(location, task.response);
- };
- }
- // ......
- }
- 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:这个方法:
- + (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
- // 因为af_resume和af_suspend都是类的实例方法,所以使用class_getInstanceMethod获取这两个方法
- Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
- Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
- // 给theClass添加一个名为af_resume的方法,使用@selector(af_resume)获取方法名,使用afResumeMethod作为方法实现
- if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
- // 交换resume和af_resume的方法实现
- af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
- }
- // 同上
- if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
- af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
- }
- }
上述方法调用了大量私有的方法,下面一一解释:
- // 根据两个方法名称交换两个方法,内部实现是先根据函数名获取到对应方法实现
- // 再调用method_exchangeImplementations交换两个方法
- 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);
- }
- // 给theClass添加名为selector,对应实现为method的方法
- static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
- // 内部实现使用的是class_addMethod方法,注意method_getTypeEncoding是为了获得该方法的参数和返回类型
- return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));
- }
- - (NSURLSessionTaskState)state {
- NSAssert(NO, @"State method should never be called in the actual dummy class");
- // 初始状态是NSURLSessionTaskStateCanceling;
- return NSURLSessionTaskStateCanceling;
- }
- - (void)af_resume {
- NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
- NSURLSessionTaskState state = [self state];
- [self af_resume]; // 因为经过method swizzling后,此处的af_resume其实就是之前的resume,所以此处调用af_resume就是调用系统的resume。但是在程序中我们还是得使用resume,因为其实际调用的是af_resume
- // 如果之前是其他状态,就变回resume状态,此处会通知调用taskDidResume
- 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];
- }
- }
解释完上面的函数后,最终回到我们的load函数:
- + (void)load {
- /**
- WARNING: 高能预警
- https://github.com/AFNetworking/AFNetworking/pull/2702
- */
- // 担心以后iOS中不存在NSURLSessionTask
- if (NSClassFromString(@"NSURLSessionTask")) {
- /**
- 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函数
- */
- // 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];
- }
- }
5. AFURLSessionManager剩余部分-NSSecureCoding和NSCopying
5.1 NSSecureCoding
关于NSSecureCoding的讲解请参考使用NSSecureCoding协议进行编解码。
因为要支持secure coding,所以要在supportsSecureCoding返回YES。
AFURLSessionManager保存的信息是其NSURLSessionConfiguration变量,然后根据获取到的configuration构建出AFURLSessionManager对象,节省了存储空间。
- + (BOOL)supportsSecureCoding {
- return YES;
- }
- - (instancetype)initWithCoder:(NSCoder *)decoder {
- NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"];
- self = [self initWithSessionConfiguration:configuration];
- if (!self) {
- return nil;
- }
- return self;
- }
5.2 NSCopying
没啥好说的,就是先构建一个AFURLSessionManager空间,并使用原先session的configuration来初始化空间内容。
- - (instancetype)copyWithZone:(NSZone *)zone {
- return [[[self class] allocWithZone:zone] initWithSessionConfiguration:self.session.configuration];
- }
讲到这,基本上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的概念),有自己的格式结构,举个例子:
- --${bound} // 该bound表示pdf的文件名
- Content-Disposition: form-data; name="Filename"
- HTTP.pdf
- --${bound} // 该bound表示pdf的文件内容
- Content-Disposition: form-data; name="file000"; filename="HTTP协议详解.pdf"
- Content-Type: application/octet-stream
- %PDF-1.5
- file content
- %%EOF
- --${bound} // 该bound表示字符串
- Content-Disposition: form-data; name="Upload"
- Submit Query
- --${bound}—// 表示body结束了
举例:比如上面那个例子,我们如果想使用multipart形式调用,应该使用怎样的调用方法?
先说结论:
- AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
- [manager POST:@"postURLString" parameters:@{@"Filename":@"HTTP.pdf"} constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
- [formData appendPartWithFileData:[pdf文件具体内容(NSData *)]
- name:@"file000"
- fileName:@"HTTP协议详解.pdf"
- mimeType:@"application/octet-stream"];
- [formData appendPartWithFormData:[@"Submit Query" dataUsingEncoding:NSUTF8StringEncoding]
- name:@"Upload"];
- } 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部分。下面我挑出了其中比较关键的代码进行分析:
- // 使用initWithURLRequest:stringEncoding:来初始化一个AFStreamingMultipartFormData变量
- // 每个AFStreamMultipartFormData其实都是对应一个上面举的那个例子,主要是为了构建bodyStream
- __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
- // 处理parameters,比如上面的@"Filename":"HTTP.pdf",首先构建一个AFQueryStringPair,其中field为"Filename",value为"HTTP.pdf"
// 然后会根据对应value的类型,构建出一个NSData变量。比如此处的value是一个NSString,所以调用//data = [[pair.value description] dataUsingEncoding:self.stringEncoding];将NSString->NSData
- if (parameters) {
- for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
- NSData *data = nil;
- if ([pair.value isKindOfClass:[NSData class]]) {
- data = pair.value;
- } else if ([pair.value isEqual:[NSNull null]]) {
- data = [NSData data];
- } else {
- data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
- }
- if (data) {
- // bodyStream构造最主要的部分就在这了(虽然后面requestByFinalizingMultipartFormData函数还会稍微处理一下)
- // 根据data和name构建Request的header和body,后面详解
- [formData appendPartWithFormData:data name:[pair.field description]];
- }
- }
- }
- // 参考上面的例子,其实还是往formData中添加数据
- if (block) {
- block(formData);
- }
- // 做最终的处理,比如设置一下MultipartRequest的bodyStream或者其特有的content-type等等,后面也会详解
- return [formData requestByFinalizingMultipartFormData];
至于AFStreamMultipartFormData类,appendPartWithFormData:和requestByFinalizingMultipartFormData等等函数,我大概看了下内容还是比较多的,准备在下一篇中介绍AFURLRequestSerialization时详细介绍。此处我们只需要知道这里构建了一个Multipart Request给uploadTask构造时使用。
6.2 NSSecureCoding
- // 对baseURL,session.configuration,requestSerializer,responseSerializer,securityPolicy进行编码
- - (void)encodeWithCoder:(NSCoder *)coder {
- // AFHTTPSessionManager的父类为AFURLSessionManager,所以先调用父类方法
- [super encodeWithCoder:coder];
- // 因为configuration是一个对象,所以要考虑是否实现了NSCoding
- [coder encodeObject:self.baseURL forKey:NSStringFromSelector(@selector(baseURL))];
- if ([self.session.configuration conformsToProtocol:@protocol(NSCoding)]) {
- [coder encodeObject:self.session.configuration forKey:@"sessionConfiguration"];
- } else {
- [coder encodeObject:self.session.configuration.identifier forKey:@"identifier"];
- }
- [coder encodeObject:self.requestSerializer forKey:NSStringFromSelector(@selector(requestSerializer))];
- [coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))];
- [coder encodeObject:self.securityPolicy forKey:NSStringFromSelector(@selector(securityPolicy))];
- }
对于initWithCoder:就不赘述了。
6.3 NSCopying
- // 深拷贝,递归地拷贝下去
- - (instancetype)copyWithZone:(NSZone *)zone {
- AFHTTPSessionManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL sessionConfiguration:self.session.configuration];
- HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone];
- HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone];
- HTTPClient.securityPolicy = [self.securityPolicy copyWithZone:zone];
- return HTTPClient;
- }
写NSSecureCoding和NSCopying的目的,不是因为这两个函数有什么难度在里面,而是为了时刻提醒自己,还要记得这两个协议,学会使用它们。后面除非这两个协议有特殊处理,就不讨论了。
7. AFURLSessionManagerTests和AFHTTPSessionManagerTests
主要还是利用https://httpbin.org/提供的各种借口进行测试,比如重定向使用/redirect/1测试,状态码返回204使用/status/204测试等等。本文不想过多介绍httpbin网站内容,大家感兴趣,自行研究。另外test中有很多类似函数使用的例子可以作为参考,比如POST等等函数的使用方法,所以还是值得看看的,这里我就不费口舌了。
8. 总结
这一篇比较零散,主要是给AFURLSessionManager和AFHTTPSessionManager两个文件擦屁股的。所以有些问题请结合之前的文章一起来看。下面几篇就比较单纯了,比如request序列化,response序列化、安全策略和网络状态管理这几个模块可以按独立部分来学习。
9. 参考文章
- AFNetworking之iOS7以上
- iOS - + initialize 与 +load
- 使用NSSecureCoding协议进行编解码
- HTTP协议之multipart/form-data请求分析
【原】AFNetworking源码阅读(四)的更多相关文章
- 【原】AFNetworking源码阅读(六)
[原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...
- 【原】AFNetworking源码阅读(五)
[原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...
- 【原】AFNetworking源码阅读(二)
[原]AFNetworking源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中我们在iOS Example代码中提到了AFHTTPSessionMa ...
- 【原】AFNetworking源码阅读(一)
[原]AFNetworking源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 AFNetworking版本:3.0.4 由于我平常并没有经常使用AFNetw ...
- 【原】AFNetworking源码阅读(三)
[原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data tas ...
- 39 网络相关函数(七)——live555源码阅读(四)网络
39 网络相关函数(七)——live555源码阅读(四)网络 39 网络相关函数(七)——live555源码阅读(四)网络 简介 14)readSocket从套接口读取数据 recv/recvfrom ...
- 40 网络相关函数(八)——live555源码阅读(四)网络
40 网络相关函数(八)——live555源码阅读(四)网络 40 网络相关函数(八)——live555源码阅读(四)网络 简介 15)writeSocket向套接口写数据 TTL的概念 函数send ...
- 38 网络相关函数(六)——live555源码阅读(四)网络
38 网络相关函数(六)——live555源码阅读(四)网络 38 网络相关函数(六)——live555源码阅读(四)网络 简介 12)makeSocketNonBlocking和makeSocket ...
- 37 网络相关函数(五)——live555源码阅读(四)网络
37 网络相关函数(五)——live555源码阅读(四)网络 37 网络相关函数(五)——live555源码阅读(四)网络 简介 10)MAKE_SOCKADDR_IN构建sockaddr_in结构体 ...
随机推荐
- react组件的生命周期
写在前面: 阅读了多遍文章之后,自己总结了一个.一遍加强记忆,和日后回顾. 一.实例化(初始化) var Button = React.createClass({ getInitialState: f ...
- gentoo 安装
加载完光驱后 1进行ping命令查看网络是否通畅 2设置硬盘的标识为GPT(主要用于64位且启动模式为UEFI,还有一个是MBR,主要用于32位且启动模式为bois) parted -a optima ...
- Web Api 入门实战 (快速入门+工具使用+不依赖IIS)
平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html 屁话我也就不多说了,什么简介的也省了,直接简单概括+demo ...
- .NET Core的日志[4]:将日志写入EventLog
面向Windows的编程人员应该不会对Event Log感到陌生,以至于很多人提到日志,首先想到的就是EventLog.EventLog不仅仅记录了Windows系统自身针对各种事件的日志,我们的应用 ...
- 6. ModelDriven拦截器、Preparable 拦截器
1. 问题 Struts2 的 Action 我们将它定义为一个控制器,但是由于在 Action 中也可以来编写一些业务逻辑,也有人会在 Action 输入业务逻辑层. 但是在企业开发中,我们一般会将 ...
- Win.ini和注册表的读取写入
最近在做打包的工作,应用程序的配置信息可以放在注册表文件中,但是在以前的16位操作系统下,配置信息放在Win.ini文件中.下面介绍一下Win.ini文件的读写方法和注册表的编程. 先介绍下Win.i ...
- C#中将DataTable导出为HTML的方法
今天我要向大家分享一种将DataTable导出为到HTML格式的方法.有时我们需要HTML格式的输出数据, 以下代码就可以帮助我们达到目的,. 首先,我们要绑定DataTable和 DataGridV ...
- PHP 获取 特定时间范围 类
目录 前序 用途 功能及事项 使用方法 代码及注释 前序: 总体来说,我更应该是一个 android 移动开发者,而不是一个 phper,如果说只做移动端的 APP ,我也不会学这么多,这 2年来, ...
- AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager
让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...
- ZKWeb网页框架1.4正式发布
本次更新的内容有 添加更快的批量操作函数 添加IDatabaseContext.FastBatchSave 添加IDatabaseContext.FastBatchDelete 注意这些函数不会触发注 ...