本文转载请注明出处 —— polobymulberry-博客园html
上一篇还遗留了不少问题,包括AFURLSessionManagerTaskDelegate类所实现的NSURLSession相关的代理方法,甚至连dataTask、uploadTask、downloadTask这几个基本概念也没说。这一篇就是为了集中消灭这些遗留问题。ios
此处实现的仍然是NSURLSession相关的代理方法,由于上一篇中已经详细介绍过了,因此对应的相关方法介绍就不赘述,直接介绍方法实现。git
该函数在AFURLSessionManager中的- URLSession:task:didCompleteWithError:被调用github
函数实现:web
- (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 }
该函数在AFURLSessionManager中的- URLSession:dataTask:didReceiveData:被调用安全
函数实现:网络
- (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { // 将每次得到的新数据附在mutableData上,来组成最终得到的全部数据 [self.mutableData appendData:data]; }
该函数在AFURLSessionManager中的- URLSession:downloadTask:didFinishDownloadingToURL:被调用
函数实现:session
和AFURLSessionManager中的实现相似,这里就不赘述了。app
首先简单介绍下session task,如下语句引用自从 NSURLConnection 到 NSURLSessionasync
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构建方法:
对应AFNetworking中的uploadTaskWithRequest:fromFile:progress:completionHandler:方法,关于这个方法,里面使用到了attemptsToRecreateUploadTasksForBackgroundSessions变量,这个是用于建立后台task时使用的。由于在iOS7中,有时候建立后台task会失败,Apple建议若是建立失败了,就从新尝试建立。此处尝试的次数最大为AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask,默认为3。至于其中使用到的addDelegateForUploadTask:地实现基本同addDelegateForDataTask:实现。
详见源码。
对应AFNetworking中的uploadTaskWithRequest:fromData:progress:completionHandler:方法。
详见源码。
对应AFNetworking中的uploadTaskWithRequest:fromData:progress:completionHandler:方法。
详见源码。
2. 系统提供的downloadTask构建方法:
对应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); }; } // ...... }
对应AFNetworking中的downloadTaskWithResumeData:progress:destination:completionHandler:方法。
这个类在#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]; } }
关于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; }
没啥好说的,就是先构建一个AFURLSessionManager空间,并使用原先session的configuration来初始化空间内容。
- (instancetype)copyWithZone:(NSZone *)zone { return [[[self class] allocWithZone:zone] initWithSessionConfiguration:self.session.configuration]; }
讲到这,基本上AFURLSessionManager这个文件的内容已经东一点西一点讲完了。下面,咱们再来跳到AFHTTPSessionManager这个文件中,看看还有哪些内容没有讲完。
这个带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构造时使用。
// 对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:就不赘述了。
// 深拷贝,递归地拷贝下去 - (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的目的,不是由于这两个函数有什么难度在里面,而是为了时刻提醒本身,还要记得这两个协议,学会使用它们。后面除非这两个协议有特殊处理,就不讨论了。
主要仍是利用https://httpbin.org/提供的各类借口进行测试,好比重定向使用/redirect/1测试,状态码返回204使用/status/204测试等等。本文不想过多介绍httpbin网站内容,你们感兴趣,自行研究。另外test中有不少相似函数使用的例子能够做为参考,好比POST等等函数的使用方法,因此仍是值得看看的,这里我就不费口舌了。
这一篇比较零散,主要是给AFURLSessionManager和AFHTTPSessionManager两个文件擦屁股的。因此有些问题请结合以前的文章一块儿来看。下面几篇就比较单纯了,好比request序列化,response序列化、安全策略和网络状态管理这几个模块能够按独立部分来学习。