【原】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 个实体子类可以直接使用: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:

(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源码阅读(四)的更多相关文章

  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. javascript动画系列第三篇——碰撞检测

    前面的话 前面分别介绍了拖拽模拟和磁性吸附,当可视区域内存在多个可拖拽元素,就出现碰撞检测的问题,这也是javascript动画的一个经典问题.本篇将详细介绍碰撞检测 原理介绍 碰撞检测的方法有很多, ...

  2. ES5对Array增强的9个API

    为了更方便的对Array进行操作,ES5规范在Array的原型上新增了9个方法,分别是forEach.filter.map.reduce.reduceRight.some.every.indexOf ...

  3. HTML骨架结构

    前面的话   一个完整的HTML文档必须包含3个部分:文档声明.文档头部和文档主体.而正是它们构成了HTML的骨架结构.前面已经分别介绍过文档声明和文档头部,本文将详细介绍构成HTML骨架结构的基础元 ...

  4. MCDownloadManager ios文件下载管理器

    我们用AFNetworking小试牛刀,写一个简单的下载器来演示功能. 前言 为什么AFNetworking能够成为顶级框架?我们究竟该如何领悟它的精髓所在?这都是很难的问题.安全,高效,流畅,这3个 ...

  5. 微信网页开发之获取用户unionID的两种方法--基于微信的多点登录用户识别

    假设网站A有以下功能需求:1,pc端微信扫码登录:2,微信浏览器中的静默登录功能需求,这两种需求就需要用到用户的unionID,这样才能在多个登录点(终端)识别用户.那么这两种需求下用户的unionI ...

  6. C#制作简易屏保

    前言:前段时间,有个网友问我C#制作屏保的问题,我瞬间懵逼了(C#还可以制作屏保!).于是我去查阅相关资料,下面把C#如何制作屏保的过程及我学习过程的心得也记录下来,希望对需要的人能有帮助. 基本思路 ...

  7. 【深入Java虚拟机】之四:类加载机制

    类加载过程     类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.它们开始的顺序如下图所示: 其中类加载的过程包括了加载.验 ...

  8. Ajax部分

    Ajax的概念 AJAX即"Asynchronous Javascript And XML"(异步JavaScript和XML),是一种用于创建快速动态网页的技术. 动态网页:是指 ...

  9. Android开发学习—— Fragment

    #Fragment* 用途:在一个Activity里切换界面,切换界面时只切换Fragment里面的内容* 生命周期方法跟Activity一致,可以理解把其为就是一个Activity* 定义布局文件作 ...

  10. SQL 约束

    先用设计器创建约束.再用代码创建约束.数据库约束是为了保证数据的完整性(正确性)而实现的一套机制见文件Employee.sql非空约束(选择复选框)主键约束(PK) primary key const ...