【原】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. EntityFramework Core Raw SQL

    前言 本节我们来讲讲EF Core中的原始查询,目前在项目中对于简单的查询直接通过EF就可以解决,但是涉及到多表查询时为了一步到位就采用了原始查询的方式进行.下面我们一起来看看. EntityFram ...

  2. 隐私泄露杀手锏 —— Flash 权限反射

    [简版:http://weibo.com/p/1001603881940380956046] 前言 一直以为该风险早已被重视,但最近无意中发现,仍有不少网站存在该缺陷,其中不乏一些常用的邮箱.社交网站 ...

  3. 运行执行sql文件脚本的例子

    sqlcmd -s -d db_test -r -i G:\test.sql 黑色字体为关键命令,其他颜色(从左至右):服务器名称,用户名,密码,数据库,文件路径 通过select @@servern ...

  4. OpenCASCADE Expression Interpreter by Flex & Bison

    OpenCASCADE Expression Interpreter by Flex & Bison eryar@163.com Abstract. OpenCASCADE provide d ...

  5. PhotoView实现图片随手势的放大缩小的效果

    项目需求:在listView的条目中如果有图片,点击条目,实现图片的放大,并且图片可以根据手势来控制图片放大缩小的比例.类似于微信朋友圈中查看好友发布的照片所实现的效果. 思路是这样的:当点击条目的时 ...

  6. 【走过巨坑】android studio对于jni调用及运行闪退无法加载库的问题解决方案

    相信很多小伙伴都在android开发中遇到调用jni的各种巨坑,因为我们不得不在很多地方用到第三方库so文件,然而第三方官方通常都只会给出ADT环境下的集成方式,而谷歌亲儿子android studi ...

  7. LoadRunner函数百科叒叒叒更新了!

    首先要沉痛通知每周四固定栏目[学霸君]由于小编外派公干,本周暂停. 那么这周就由云层君来顶替了,当然要要说下自己做的内容啦,DuangDuang! <LoadRunner函数百科>更新通知 ...

  8. C#创建、安装、卸载、调试Windows Service(Windows 服务)的简单教程

    前言:Microsoft Windows 服务能够创建在它们自己的 Windows 会话中可长时间运行的可执行应用程序.这些服务可以在计算机启动时自动启动,可以暂停和重新启动而且不显示任何用户界面.这 ...

  9. 【干货分享】流程DEMO-出差申请单

    流程名: 出差申请  业务描述: 员工出差前发起流程申请,流程发起时,会检查预算,如果预算不够,将不允许发起费用申请,如果预算够用,将发起流程,同时占用相应金额的预算,但撤销流程会释放相应金额的预算. ...

  10. struts2国际化

    struts2国际化 1:什么是国际化? 国际化(internationalization)是设计和制造容易适应不同区域要求的产品的一种方式.它要求从产品中抽离所有的与语言,国家/地区和文化相关的元素 ...