IOS框架:AFNetworking(上)
原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、功能模块
- 二、源码解析
- 1、初始化方法
- 2、AFHTTPSessionManager中GET请求方法的源码实现
- a、request的拼接
- b、生成dataTask
- 3、AFURLSessionManager 中代理方法的实现
- a、NSURLSessionDelegate的实现
- b、NSURLSessionDelegate转发到AF自定义的deleagate
- 4、AFURLResponseSerialization 如何解析数据
- 5、AF整个流程和线程的关系
- 三、AF2.x版本的核心实现
- 1、Get请求
- 2、AFHTTPRequestOperationManager的初始化方法
- 3、Get方法的实现
- 4、connection的代理方法
- 5、通过setCompletionBlockWithSuccess方法接收responseData
- 6、数据解析
- 7、问题:为什么AF2.x需要一条常驻线程?
- 四、AFNetworking的作用总结
- 五、AFSecurityPolicy实现https认证需求
- 1、NSURLSessionDelegate中的代理方法:didReceiveChallenge
- 2、AFSecurityPolicy实现https认证
- a、创建AFSecurityPolicy
- b、evaluateServerTrust:方法的内部实现
- 3、自签名的证书
- 六、UIKit扩展与缓存实现
- 1、AFNetworkActivityIndicatorManager :网络请求时状态栏的小菊花
- a、使用方式
- b、初始化
- 2、UIImageView+AFNetworking :请求网络图片
- a、图片下载类AFImageDownloader的初始化方法
- b、图片下载类AFImageDownloader创建请求task的方法
- c、图片缓存类AFAutoPurgingImageCache的初始化方法
- d、图片缓存类AFAutoPurgingImageCache的核心方法
- e、setImageWithURL 设置图片方法
- f、总结请求图片、缓存、设置图片的流程
- 1、AFNetworkActivityIndicatorManager :网络请求时状态栏的小菊花
- Demo
- 参考文献
作为一个iOS开发,也许你不知道NSUrlRequest
、不知道NSUrlConnection
、也不知道NSURLSession
...(说不下去了...怎么会什么都不知道...)但是你一定知道AFNetworking
。大多数人习惯了只要是请求网络都用AF,但是你真的知道AF做了什么吗?为什么我们不用原生的NSURLSession
而选择AFNetworking
? 本文将从源码的角度去分析AF的实际作用。
一、功能模块
- 网络通信模块(
AFURLSessionManager
、AFHTTPSessionManger
) - 网络状态监听模块(
Reachability
) - 网络通信安全策略模块(
Security
) - 网络通信信息序列化/反序列化模块(
Serialization
) - 对于
iOS UIKit
库的扩展(UIKit
)

核心当然是网络通信模块AFURLSessionManager
。AF3.x是基于NSURLSession
来封装的。所以这个类围绕着NSURLSession
做了一系列的封装。而其余的四个模块,均是为了配合网络通信或对已有UIKit
的一个扩展工具包。
这五个模块所对应的类的结构关系图如下所示:

其中AFHTTPSessionManager
是继承于AFURLSessionManager
的,我们一般做网络请求都是用这个类,但是它本身是没有做实事的,只是做了一些简单的封装,把请求逻辑分发给父类AFURLSessionManager
或者其它类去做。
二、源码解析
简单的写个get
请求:
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
[manager GET:@"http://localhost" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
1、初始化方法
可以看到,调用了初始化方法生成了一个manager
,点进去看看初始化做了什么:
+ (instancetype)manager {
return [[[self class] alloc] initWithBaseURL:nil];
}
- (instancetype)init {
return [self initWithBaseURL:nil];
}
- (instancetype)initWithBaseURL:(NSURL *)url {
return [self initWithBaseURL:url sessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
return [self initWithBaseURL:nil sessionConfiguration:configuration];
}
经过层层调用,终于到了- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration
方法,看看它的实现:
/*
1.调用父类的方法
2.给url添加“/”
3.给requestSerializer、responseSerializer设置默认值
*/
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
// 调用父类初始化方法
self = [super initWithSessionConfiguration:configuration];
if (!self) {
return nil;
}
// Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
// 为了确保NSURL +URLWithString:relativeToURL: works可以正确执行,在baseurlpath的最后添加‘/’
// url有值且最后不包含‘/’,那么在url的末尾添加‘/’
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
self.baseURL = url;
// 给requestSerializer、responseSerializer设置默认值
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
初始化方法都调用父类的初始化方法。父类也就是AF3.x最最核心的类AFURLSessionManager
。除此之外,方法中把baseURL
存了起来,还生成了一个请求序列对象和一个响应序列对象。
那么看看父类AFURLSessionManager
的初始化方法又作了什么:
// 构造函数
- (instancetype)init {
return [self initWithSessionConfiguration:nil];
}
/*
1.初始化一个session
2.给manager的属性设置初始值
*/
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
// 设置默认的configuration,配置我们的session
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
// 持有configuration
self.sessionConfiguration = configuration;
// 设置为delegate的操作队列并发的线程数量1,也就是串行队列
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
/*
* 如果完成后需要做复杂(耗时)的处理,可以选择异步队列
* 如果完成后直接更新UI,可以选择主队列 [NSOperationQueue mainQueue]
*/
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
// 默认为json解析
self.responseSerializer = [AFJSONResponseSerializer serializer];
// 设置默认证书 无条件信任证书https认证
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
// 网络状态监听
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
// delegate= value taskid = key
// 设置存储NSURL task与AFURLSessionManagerTaskDelegate的词典
// 每一个task都会被匹配一个AFURLSessionManagerTaskDelegate来做task的delegate,进行事件处理
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
// 使用NSLock确保词典在多线程访问时的线程安全
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
// 置空task关联的代理
// 异步的获取当前session的所有未完成的task
// 其实讲道理来说在初始化中调用这个方法应该里面一个task都不会有,打断点去看,也确实如此,里面的数组都是空的
// 当后台任务重新回来初始化session,可能就会有先前的请求任务,导致程序的crash
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
return self;
}
2、AFHTTPSessionManager中GET请求方法的源码实现
方法走到类AFHTTPSessionManager
中来,调用父类,也就是我们整个AF3.x的核心类AFURLSessionManager
的方法,生成了一个系统的NSURLSessionDataTask
实例,并且开始网络请求。
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
// 返回一个task,然后开始网络请求
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
// 开始网络请求
[dataTask resume];
return dataTask;
}
进入- dataTaskWithHTTPMethod:
方法中,看下具体实现:
//1.生成request
//2.通过request生成task
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
// 处理request构建产生的错误
NSError *serializationError = nil;
// 1、先调用AFHTTPRequestSerializer的requestWithMethod函数构建request
// relativeToURL表示将URLString拼接到baseURL后面
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
// 如果解析错误,直接返回
if (serializationError) {
if (failure) {
// diagnostic:诊断的,常用push pop搭配,来忽略一些编译器的警告
// 这里是用来忽略:?带来的警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
// completionQueue是我们自定义的,是一个GCD的Queue
// 如果设置了那么从这个Queue中回调结果,不存在则从主队列回调
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
}
return nil;
}
// 2、此时的request已经将参数拼接在url后面,根据request来生成dataTask
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
// 在完成的回调里,调用我们传过来的成功和失败的回调
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
return dataTask;
}
该方法做了两件事:
1、先调用AFHTTPRequestSerializer
的requestWithMethod
函数构建request
。
2、此时的request
已经将参数拼接在url
后面,根据request
来生成dataTask
。
所以分别从request
和dataTask
的生成来探究。
a、生成request
继续深入到- requestSerializer:
方法中,探究如何拼接成成需要的request
:
/**
* 使用指定的HTTP method和URLString来构建一个NSMutableURLRequest对象实例
* 如果method是GET、HEAD、DELETE,那parameter将会被用来构建一个基于url编码的查询字符串(query url),并且这个字符串会直接加到request的url后面。对于其他的Method,比如POST/PUT,它们会根
据parameterEncoding属性进行编码,而后加到request的http body上。
* @param method request的HTTP methodt,比如 `GET`, `POST`, `PUT`, or `DELETE`. 该参数不能为空
* @param URLString 用来创建request的URL
* @param parameters 既可以对method为GET的request设置一个查询字符串(query string),也可以设置到request的HTTP body上
* @param error 构建request时发生的错误
* @return 一个NSMutableURLRequest的对象
*/
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
// 断言,debug模式下,如果缺少改参数为nil,crash并直接打印出来
NSParameterAssert(method);
NSParameterAssert(URLString);
// 我们传进来的是一个字符串,在这里它帮你转成url
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
// 设置请求方式(get、post、put.....)
mutableRequest.HTTPMethod = method;
// 将request的各种属性遍历
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
// 将观察到的发生变化的属性,添加到NSMutableURLRequest(如:timeout)
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
// 通过kvc动态的给mutableRequest添加value
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
// 将传入的parameters进行编码,拼接到url后并返回 count=5&start=1
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
NSLog(@"request'''''''''%@",mutableRequest);
return mutableRequest;
}
其中AFHTTPRequestSerializerObservedKeyPaths()
是一个c
函数,返回一个数组,我们来看看这个函数:
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 此处需要observer的keypath为allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies、HTTPShouldUsePipelining、networkServiceType、timeoutInterval
_AFHTTPRequestSerializerObservedKeyPaths =
@[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
// 这个函数就是封装了一些属性的名字,这些都是NSUrlRequest的属性
return _AFHTTPRequestSerializerObservedKeyPaths;
}
该函数返回一个方法名的数组。定义了一个static
的方法,表示该方法只能在本文件中使用。
-
allowsCellularAccess:是否允许使用设备的蜂窝移动网络来创建
request
,默认为允许。 -
cachePolicy:创建的
request
所使用的缓存策略,默认使用NSURLRequestUseProtocolCachePolicy
,该策略表示
如果缓存不存在,直接从服务端获取。如果缓存存在,会根据response
中的Cache-Control
字段判断
下一步操作,如:Cache-Control
字段为must-revalidata
,则询问服务端该数据是否有更新,无更新的话
直接返回给用户缓存数据,若已更新,则请求服务端。 -
HTTPShouldHandleCookies:如果设置
HTTPShouldHandleCookies
为YES
,就处理存储在NSHTTPCookieStore
中的cookies
,HTTPShouldHandleCookies
表示是否应该给request
设置cookie
并随request
一起发送出去。 -
HTTPShouldUsePipelining:表示
receiver
(理解为iOS客户端)的下一个信息是否必须等到上一个请求回复才能发送。如果为YES表示可以,NO表示必须等receiver
收到先前的回复才能发送下个信息。 -
networkServiceType:设定
request
的network service
类型,默认是NSURLNetworkServiceTypeDefault
。这个network service
是为了告诉系统网络层这个request
使用的目的,比如NSURLNetworkServiceTypeVoIP
表示的就这个request
是用来请求网际协议通话技术(Voice over IP
)。系统能根据提供的信息来优化网络处理,从而优化电池寿命,网络性能等等,客户端基本不使用。 - timeoutInterval:超时机制,默认60秒
再进入self.mutableObservedChangedKeyPaths
属性探索下:
//某个request需要观察的属性集合
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
在-init
方法里对这个集合进行了初始化,并且对当前类的和NSUrlRequest
相关的那些属性添加了KVO
监听:
- (instancetype)init {
.......
//每次都会重置变化
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
//自己给自己的方法添加观察者,就是request各种属性的set方法
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
.......
}
对应的KVO
触发的方法:
#pragma mark - NSKeyValueObserving
/**
如果kvo的触发机制是默认出发。则返回true,否则返回false。在这里,只要是`AFHTTPRequestSerializerObservedKeyPaths`里面的属性,我们都取消自动出发kvo机制,使用手动触发。
为什么手动,我猜应该是为了在监听这些属性时可以用于某些特殊的操作,比如测试这些属性变化是否崩溃等。
@param key kvo的key
@return bool值
*/
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == AFHTTPRequestSerializerObserverContext) {
// 当观察到这些set方法被调用了,而且不为Null就会添加到集合里,否则移除
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
接下来通过kvc
动态的给mutableRequest
添加value
,keyPath
为观察到的发生变化的属性:
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
最后将传入的parameters
进行编码,拼接到url
后设置到request
中去 :
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
其对应的实现方法- requestBySerializingRequest:
方法为:
#pragma mark - AFURLRequestSerialization
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 1、从self.HTTPRequestHeaders里去遍历拿到设置的参数,如果request此项无值则给mutableRequest.headfiled赋值
/*
1.请求行(状态行):get,url,http协议1.1
2.请求头:conttent-type,accept-language
3.请求体:get/post get参数拼接在url后面 post数据放在body
*/
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
// 2、将我们传入的字典转成字符串,具体转码方式,我们可以使用自定义的方式,也可以用AF默认的转码方式
NSString *query = nil;
if (parameters) {
// 自定义的解析方式
if (self.queryStringSerialization) {
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
//默认解析方式,dictionary count=5&start=1
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
// 将parameters传入这个c函数
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
// count=5&start=1
NSLog(@"query:%@",query);
// 3、最后判断该request中是否包含了GET、HEAD、DELETE(都包含在HTTPMethodsEncodingParametersInURI)
// 因为这几个method的query是拼接到url后面的
// 而POST、PUT是把query拼接到http body中的
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {// post put请求
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
//函数会判断request的Content-Type是否设置了,如果没有,就默认设置为application/x-www-form-urlencoded
//application/x-www-form-urlencoded是常用的表单发包方式,普通的表单提交,或者js发包,默认都是通过这种方式
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
//设置请求体
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
自定义的方式想怎么去解析由你自己来决定,而默认的方式:
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];//一对
//把参数传给AFQueryStringPairsFromDictionary,AFQueryStringPair数据处理类
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
//百分号编码
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
//拆分数组返回参数字符串
return [mutablePairs componentsJoinedByString:@"&"];
}
这个方法�,是遍历数组中的AFQueryStringPair
,然后以&
符号拼接。
AFQueryStringPair
是一个数据处理类,只有两个属性:field
和value
;一个方法:- URLEncodedStringValue
。它的作用就是上面我们说的,以key=value
的形式,用URL Encode
编码,拼接成字符串。
@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (instancetype)initWithField:(id)field value:(id)value;
- (NSString *)URLEncodedStringValue;
@end
// 百分号编码 count=5 ASCII uinicode
- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return AFPercentEscapedStringFromString([self.field description]);
} else {
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
}
}
至于AFQueryStringPairsFromDictionary
,它只是起过渡作用,往下继续调用:
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
在AFQueryStringPairsFromKeyAndValue
中使用了递归,会对value
的类型进行判断,有NSDictionary
、NSArray
、NSSet
类型。
不过有人就会问了,在AFQueryStringPairsFromDictionary
中给AFQueryStringPairsFromKeyAndValue
函数传入的value
不是NSDictionary
嘛?还要判断那么多类型干啥?对,问得很好,这就是AFQueryStringPairsFromKeyAndValue
的核心----递归调用并解析,你不能保证NSDictionary
的value
中存放的不是一个NSArray
、NSSet
。
既然是递归,那么就要有结束递归的情况,比如解析到最后,对应value
是一个NSString
,那么就得调用函数中最后的else
语句,
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {//key=count value = 5
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
// 1、根据需要排列的对象的description来进行升序排列,并且selector使用的是compare:
// 因为对象的description返回的是NSString,所以此处compare:使用的是NSString的compare函数
// 比如:@[@"foo", @"bar", @"bae"] ----> @[@"bae", @"bar",@"foo"]
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
// 2、判断vaLue是什么类型的,然后去递归调用自己,直到解析的是除了array dic set以外的元素,然后把得到的参数数组返回。
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];//nestedkey=count nestedvalue=5
if (nestedValue) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {//既然是递归,那么就要有结束递归的情况,比如解析到最后,对应value是一个NSString,那么就得调用函数中最后的else语句,
//AFQueryStringPair数据处理类,mutableQueryStringComponents中的元素类型是AFQueryStringPair
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
举个例子演示下整个转码过程:
@{
@"name" : @"bang",
@"phone": @{@"mobile": @"xx", @"home": @"xx"},
@"families": @[@"father", @"mother"],
@"nums": [NSSet setWithObjects:@"1", @"2", nil]
}
->
@[
field: @"name", value: @"bang",
field: @"phone[mobile]", value: @"xx",
field: @"phone[home]", value: @"xx",
field: @"families[]", value: @"father",
field: @"families[]", value: @"mother",
field: @"nums", value: @"1",
field: @"nums", value: @"2",
]
->
name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2
目的是将原来的容器类型的参数变成字符串类型。
紧接着该方法还根据request
中请求类型,来判断参数字符串应该如何设置到request
中去。如果是GET
、HEAD
、DELETE
,则把参数quey
拼接到url
后面。如果是POST
、PUT
,则把query
拼接到http body
中:
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {// GET、HEAD、DELETE
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {// post put请求
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
//函数会判断request的Content-Type是否设置了,如果没有,就默认设置为application/x-www-form-urlencoded
//application/x-www-form-urlencoded是常用的表单发包方式,普通的表单提交,或者js发包,默认都是通过这种方式
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
//设置请求体
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
最后,返回生成的request
即可。
b、生成dataTask
生成的方法在核心类AFURLSessionManger
中:
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
// iOS 8.0以下版本中会并发地创建多个task对象,而同步有没有做好,导致taskIdentifiers 不唯一
// 1、为了解决这个bug,调用一个串行队列来创建dataTask
__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
// 系统原生的方法,使用session来创建一个NSURLSessionDataTask对象
dataTask = [self.session dataTaskWithRequest:request];
});
// 2、为什么要给task添加代理呢?进去看下
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
调用了一个url_session_manager_create_task_safely()
函数,传了一个Block
进去,Block
里就是iOS原生生成dataTask
的方法,看看url_session_manager_create_task_safely()
的实现:
static void url_session_manager_create_task_safely(dispatch_block_t block) {
NSLog(@"NSFoundationVersionNumber = %f",NSFoundationVersionNumber);
if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
// 为什么用sync,因为是想要主线程等在这,等执行完再返回,因为必须执行完dataTask才有数据,传值才有意义。
// 为什么要用串行队列,因为这块是为了防止ios8以下NSURLSession内部的dataTaskWithRequest是并发创建的
// 这样会导致taskIdentifiers这个属性值不唯一,因为后续要用taskIdentifiers来作为Key对应delegate
dispatch_sync(url_session_manager_creation_queue(), block);//同步
} else {
block();
}
}
// 创建一个用于创建task的串行队列
static dispatch_queue_t url_session_manager_creation_queue() {
static dispatch_queue_t af_url_session_manager_creation_queue;
// 保证了即使是在多线程的环境下,也不会创建其他队列
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
});
return af_url_session_manager_creation_queue;
}
上面的疑问解决了,那么为什么要给task
添加代理呢?也点进去看下:
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
首先注意- addDelegateForDataTask:
这个方法并不是AFURLSessionManagerTaskDelegate
的方法,而只是AFURLSessionManager
的一个方法。 然后因为这个方法内部实现有点儿绕,所以一步步分析下:
// 初始化delegate,请求传来的参数,都赋值给这个AF的代理了
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
// 代理把AFURLSessionManager这个类作为属性了
delegate.manager = self;
//weak防止循环引用(manager持有task,task和delegate是绑定的,相当于manager是持有delegate的)
@property (nonatomic, weak) AFURLSessionManager *manager;
taskDescription
是自行设置的,区分是否是当前的session
创建的。其在用来发送开始和挂起通知的时候会用到,就是用这个值来Post
通知。
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
函数字面意思是将一个session
的task
和一个AFURLSessionManagerTaskDelegate
类型的delegate
变量绑在一起,而这个绑在一起的工作是由我们的AFURLSessionManager
所做。至于绑定的过程,就是以该session task
的taskIdentifier
为key
,delegate
为value
,需要确保task
唯一,赋值给mutableTaskDelegatesKeyedByTaskIdentifier
这个NSMutableDictionary
类型的变量。知道了这两者是关联在一起的话,马上就会产生另外的问题 —— 为什么要关联以及怎么关联在一起?
[self setDelegate:delegate forTask:dataTask];
//为task设置关联的delegate
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
//task和delegate都不能为空
NSParameterAssert(task);
NSParameterAssert(delegate);
//加锁确保中间代码块是原子操作,线程安全
[self.lock lock];
//将delegate存入字典,以taskid作为key,说明每个task都有各自的代理
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
//设置两个NSProgress的变量 - uploadProgress和downloadProgress,给session task添加了两个KVO监听事件
[delegate setupProgressForTask:task];
//添加task开始和暂停的通知
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
进入到[delegate setupProgressForTask:task];
探索下:
#pragma mark - NSProgress Tracking
- (void)setupProgressForTask:(NSURLSessionTask *)task {
__weak __typeof__(task) weakTask = task;
//拿到上传下载期望的数据大小
self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
//设置这两个NSProgress对应的cancel、pause和resume这三个状态,正好对应session task的cancel、suspend和resume三个状态
//所以可以将上传与下载进度和任务绑定在一起,直接cancel suspend resume进度条,可以cancel、suspend和resume任务
[self.uploadProgress setCancellable:YES];
[self.uploadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask cancel];
}];
[self.uploadProgress setPausable:YES];
[self.uploadProgress setPausingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask suspend];
}];
if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
[self.uploadProgress setResumingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask resume];
}];
}
[self.downloadProgress setCancellable:YES];
[self.downloadProgress setCancellationHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask cancel];
}];
[self.downloadProgress setPausable:YES];
[self.downloadProgress setPausingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask suspend];
}];
if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
[self.downloadProgress setResumingHandler:^{
__typeof__(weakTask) strongTask = weakTask;
[strongTask resume];
}];
}
//给task和progress添加kvo
//观察task的这些属性
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))
options:NSKeyValueObservingOptionNew
context:NULL];
[task addObserver:self
forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))
options:NSKeyValueObservingOptionNew
context:NULL];
//观察progress这两个属性
//fractionCompleted:任务已经完成的比例,取值为0~1
[self.downloadProgress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
[self.uploadProgress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
}
而对应KVO
的回调方法为:
//KVO回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
//是task
if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
//给进度条赋新值
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
}
}
//上面的赋新值会触发这两个调用block的回调,用户可以拿到进度
//根据NSProgress的状态做用户自定义的行为,比如需要更新UI进度条的状态之类的
else if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}
如果task
触发KVO,则给progress
进度赋值。又因为progress
进度赋值了,所以也会触发progress
的KVO
,从而也会调用到这里,然后去执行我们传进来的downloadProgressBlock
和uploadProgressBlock
。说白了主要的作用就是为了让进度实时的传递,不过设计很巧妙。
setProgress
和这个KVO
监听,都是在我们AF自定义的delegate
内的。有一个task
就会有一个delegate
,所以每个task
都会分别在各自的AF
代理内去监听这些属性。
跳出深度探索回到主线剧情,最后设置AF delegate
的上传进度块,下载进度块。
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
这样就了解了- addDelegateForDataTask:
这个方法,也完成了生成dataTask
的探索任务。感觉就是不断地深入深入,可惜我水平太浅,只知道做了什么,却还无法捕获到设计思想。
3、AFURLSessionManager 中代理方法的实现
a、NSURLSessionDelegate的实现
第一部分中,探索父类AFURLSessionManager
的初始化方法时,有句代码把AFUrlSessionManager
作为了所有的task
的delegate
。
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
.......
/*
* 如果完成后需要做复杂(耗时)的处理,可以选择异步队列
* 如果完成后直接更新UI,可以选择主队列 [NSOperationQueue mainQueue]
*/
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
.......
}
这样当我们请求网络的时候,AFUrlSessionManager
实现的这一大堆NSUrlSession
相关的代理开始调用了:

其中有3条重要的,它们转发到了AFURLSessionManagerTaskDelegate
即AF自定义的代理,负责把每个task
对应的数据回调出去:

@interface AFURLSessionManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying>
@protocol NSURLSessionDelegate <NSObject>
@protocol NSURLSessionTaskDelegate <NSURLSessionDelegate>
@protocol NSURLSessionDataDelegate <NSURLSessionTaskDelegate>
@protocol NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
@protocol NSURLSessionStreamDelegate <NSURLSessionTaskDelegate>
可以看到这些代理都是继承关系,而在NSURLSession
实现中,只要设置了这个代理,它会去判断这些所有的代理,是否respondsToSelector
这些代理中的方法,如果响应了就会去调用。
AFURLSessionManager
重写了respondsToSelector
方法。在里面复写了selector
的方法,这几个方法是在本类有实现的,但是如果外面的Block
没赋值的话,则返回NO
,相当于没有实现!这样如果没实现这些我们自定义的Block
也不会去回调这些代理。因为本身某些代理,只执行了这些自定义的Block
,如果Block
都没有赋值,那我们调用代理也没有任何意义。
//复写了selector的方法,这几个方法是在本类有实现的,但是如果外面的Block没赋值的话,则返回NO,相当于没有实现!
- (BOOL)respondsToSelector:(SEL)selector {
if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) {
return self.taskWillPerformHTTPRedirection != nil;
} else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) {
return self.dataTaskDidReceiveResponse != nil;
} else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) {
return self.dataTaskWillCacheResponse != nil;
} else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) {
return self.didFinishEventsForBackgroundURLSession != nil;
}
return [[self class] instancesRespondToSelector:selector];
}
而AFURLSessionManager
的自定义Block
包括:
//block的命名
typedef void (^AFURLSessionDataTaskDidBecomeDownloadTaskBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask);
typedef void (^AFURLSessionDataTaskDidReceiveDataBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data);
.......
@property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
@property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession;
@property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidReceiveAuthenticationChallengeBlock taskDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyData;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidComplete;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadTask;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveData;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskWillCacheResponseBlock dataTaskWillCacheResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidWriteDataBlock downloadTaskDidWriteData;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidResume;
// block对应的set方法
- (void)setDataTaskDidBecomeDownloadTaskBlock:(void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block {
self.dataTaskDidBecomeDownloadTask = block;
}
- (void)setDataTaskDidReceiveDataBlock:(void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block {
self.dataTaskDidReceiveData = block;
}
作者用@property
把这个些Block
属性在.m
文件中声明,然后复写了set
方法,接着在.h
中去声明这些set
方法:
- (void)setDataTaskDidBecomeDownloadTaskBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block;
- (void)setDataTaskDidReceiveDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block;
.......
为什么要绕这么一大圈呢?前辈说是为了使用人员使用起来方便,调用set
方法去设置这些Block
,能很清晰的看到Block
的各个参数与返回值,精髓的编程思想无处不体现......唉,估计我这条咸鱼就只能看看别人设计好的代码了,叫我自己去如此精细的创造太难了,果然我就是个平凡学校里的平凡人物,跟这些天纵之才差距太大了。
接下来依次讲述这些代理方法做了什么:
❶ NSURLSessionDelegate
代理一:didBecomeInvalidWithError
当前session
失效,会调用。 如果你使用finishTasksAndInvalidate
函数使该session
失效,那么session
首先会先完成最后一个task
,然后再调用URLSession:didBecomeInvalidWithError:
代理方法。如果你调用invalidateAndCancel
方法来使session
失效,那么该session
会立即调用这个代理方法。
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
if (self.sessionDidBecomeInvalid) {
self.sessionDidBecomeInvalid(session, error);
}
// 不过源代码中没有举例如何使用这个Notification,所以需要用户自己定义,比如结束进度条的显示啊
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}
代理2:didReceiveChallenge
web
服务器接收到客户端请求时,有时候需要先验证客户端是否为正常用户,再决定是够返回真实数据。这种情况称之为服务端要求客户端接收挑战(NSURLAuthenticationChallenge *challenge
)。接收到挑战后,客户端要根据服务端传来的challenge
来生成completionHandler
所需的NSURLSessionAuthChallengeDisposition disposition
和NSURLCredential *credential
(disposition
指定应对这个挑战的方法,而credential
是客户端生成的挑战证书,注意只有challenge
中认证方法为NSURLAuthenticationMethodServerTrust
的时候,才需要生成挑战证书)。最后调用completionHandler
回应服务器端的挑战。
//收到服务端的challenge,例如https需要验证证书等 ats开启
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
/*
NSURLSessionAuthChallengeUseCredential:使用指定的证书
NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战
NSURLSessionAuthChallengeRejectProtectionSpace:拒绝此挑战,并尝试下一个验证保护空间;忽略证书参数
*/
//1. 挑战处理类型为默认
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;//证书
if (self.sessionDidReceiveAuthenticationChallenge) {//2. 自定义方法,用来如何应对服务器端的认证挑战
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {// 3. 倘若没有自定义Block
// 判断接收服务器挑战的方法是否是信任证书
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// 4. 只需要验证服务端证书是否安全(即https的单向认证,这是AF默认处理的认证方式,其他的认证方式,只能由我们自定义Block的实现
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
//信任评估通过,就从受保护空间里面拿出证书,回调给服务器,告诉服务器,我信任你,你给我发送数据吧
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 5. 确定挑战的方式
if (credential) {
//证书挑战
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
//默认挑战
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
//取消挑战
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
//默认挑战方式
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
//完成挑战
// 3.将信任凭证发送给服务端
if (completionHandler) {
completionHandler(disposition, credential);
}
}
代理3:URLSessionDidFinishEventsForBackgroundURLSession
在iOS中,当一个后台传输任务完成或者后台传输时需要证书,而此时你的app正在后台挂起,那么你的app在后台会自动重新启动运行,并且这个app的UIApplicationDelegate
会发送一个application:handleEventsForBackgroundURLSession:completionHandler:
消息。该消息包含了对应后台的session
的identifier
,而且这个消息会导致你的app启动。你的app随后应该先存储completion handler
,然后再使用相同的identifier
创建一个background configuration
,并根据这个background configuration
创建一个新的session
。这个新创建的session
会自动与后台任务重新关联在一起。
当你的app获取了一个URLSessionDidFinishEventsForBackgroundURLSession:
消息,这就意味着之前这个session
中已经入队的所有消息都转发出去了,这时候再调用先前存取的completion handler
是安全的。
//当session中所有已经入队的消息被发送出去后,会调用该代理方法
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
if (self.didFinishEventsForBackgroundURLSession) {
dispatch_async(dispatch_get_main_queue(), ^{
// 意味着background session中的消息已经全部发送出去了,返回到主进程执行自定义的函数
self.didFinishEventsForBackgroundURLSession(session);
});
}
}
❷ NSURLSessionTaskDelegate
代理1:willPerformHTTPRedirection
客户端告知服务器端需要HTTP
重定向。此方法只会在default session
或者ephemeral session
中调用,而在background session
中,session task
会自动重定向。
-
+defaultSessionConfiguration
:具有相同的共享NSHTTPCookieStorage
,共享NSURLCache
和共享NSURLCredentialStorage
。 -
+ephemeralSessionConfiguration
:返回一个预设配置,这个配置中不会对缓存,Cookie
和证书进行持久性的存储。这对于实现像秘密浏览这种功能来说是很理想的。 -
+backgroundSessionConfiguration:(NSString *)identifier
:后台session
不同于常规的,普通的session
,它甚至可以在应用程序挂起,退出或者崩溃的情况下运行上传和下载任务。identifier
初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程提供上下文。
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
NSURLRequest *redirectRequest = request;
// 自定义如何处理重定向请求,注意会生成一个新的request
if (self.taskWillPerformHTTPRedirection) {
redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
}
if (completionHandler) {
completionHandler(redirectRequest);
}
}
代理2:didReceiveChallenge
之前我们也有一个https
认证,功能一样,执行的内容也完全一样。区别在于这个是non-session-level
级别的认证,而之前的是session-level
级别的。
代理3:needNewBodyStream
如果task
是由uploadTaskWithStreamedRequest:
创建的,那么提供初始的request body stream
时候会调用该代理方法。或者因为认证挑战或者其他可恢复的服务器错误,而导致需要客户端重新发送一个含有body stream
的request
,这时候也会调用该代理。
//当一个session task需要发送一个新的request body stream到服务器端的时候,调用该代理方法
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
{
NSInputStream *inputStream = nil;
if (self.taskNeedNewBodyStream) {
// 自定义的获取到新的bodyStream方法
inputStream = self.taskNeedNewBodyStream(session, task);
} else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
// 拷贝一份数据出来到新的bodyStream中(即inputStream)
inputStream = [task.originalRequest.HTTPBodyStream copy];
}
if (completionHandler) {
completionHandler(inputStream);
}
}
代理4:didSendBodyData
每次发送数据给服务器,会回调这个方法,通知已经发送了多少,总共要发送多少。代理方法里也就是仅仅调用了我们自定义的Block
而已。
//上传任务的回调方法
//周期性地通知代理发送到服务器端数据的进度
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
// 如果totalUnitCount获取失败,就使用HTTP header中的Content-Length作为totalUnitCount
int64_t totalUnitCount = totalBytesExpectedToSend;
if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
if(contentLength) {
totalUnitCount = (int64_t) [contentLength longLongValue];
}
}
// 每次发送数据后的相关自定义处理,比如根据totalBytesSent来进行UI界面的数据上传显示
if (self.taskDidSendBodyData) {
self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
}
}
代理5:didCompleteWithError
task
完成之后的回调,成功和失败都会回调这里。这里的error
不会报告服务期端的error
,他表示的是客户端这边的error
,比如无法解析hostname
或者连不上host
主机。
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// 如果task是在后台完成的,可能delegate会为nil
if (delegate) {
// 把代理转发给我们绑定的delegate
[delegate URLSession:session task:task didCompleteWithError:error];
// 转发完,该task结束了,就移除对应的delegate
[self removeDelegateForTask:task];
}
//自定义Block回调
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
在这里根据task
获取相关联的AFURLSessionManagerTaskDelegate
对象:
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
//task不能为空
NSParameterAssert(task);
//上锁,通过task的唯一taskIdentifier从字典中取值,这个唯一标识是在创建task的时候NSURLSessionTask为其设置的,不需要手动设置,保证唯一性
AFURLSessionManagerTaskDelegate *delegate = nil;
[self.lock lock];
delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
[self.lock unlock];
return delegate;
}
移除跟AF代理相关的progress
和通知:
//从字典中删除task对应的delegate的key-value对
- (void)removeDelegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
[self.lock lock];
[delegate cleanUpProgressForTask:task];
[self removeNotificationObserverForTask:task];
[self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
[self.lock unlock];
}
❸ NSURLSessionDataDelegate
代理1:didReceiveResponse
告诉代理,该data task
获取到了服务器端传回的最初始回复(response
)。注意其中的completionHandler
这个block
,通过传入一个类型为NSURLSessionResponseDisposition
的变量来决定该传输任务接下来该做什么:
-
NSURLSessionResponseAllow
该task
正常进行 -
NSURLSessionResponseCancel
该task
会被取消 -
NSURLSessionResponseBecomeDownload
会调用URLSession:dataTask:didBecomeDownloadTask:
方法来新建一个download task
以代替当前的data task
该方法是可选的,除非你必须支持“multipart/x-mixed-replace”
类型的content-type
。因为如果你的request
中包含了这种类型的content-type
,服务器会将数据分片传回来,而且每次传回来的数据会覆盖之前的数据。每次返回新的数据时,session
都会调用该函数,你应该在这个函数中合理地处理先前的数据,否则会被新数据覆盖。如果你没有提供该方法的实现,那么session
将会继续任务,也就是说会覆盖之前的数据。
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
//设置默认为继续进行
NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
//自定义去设置
if (self.dataTaskDidReceiveResponse) {
disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
}
if (completionHandler) {
completionHandler(disposition);
}
}
代理2:didBecomeDownloadTask
上面的代理如果设置为NSURLSessionResponseBecomeDownload
,则会调用这个方法。比如在- URLSession:dataTask:didReceiveResponse:completionHandler:
给completionHandler
方法传递NSURLSessionResponseBecomeDownload
,就会使data task
变成download task
,而且之前的data task
不会再响应代理方法了。同样,如果设置为NSURLSessionResponseBecomeStream
则会调用到didBecomeStreamTask
代理里去,新生成一个NSURLSessionStreamTask
来替换掉之前的dataTask
。
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
if (delegate) {
// 将delegate关联的data task移除,换成新产生的download task
[self removeDelegateForTask:dataTask];
[self setDelegate:delegate forTask:downloadTask];
}
//执行自定义Block
if (self.dataTaskDidBecomeDownloadTask) {
self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
}
}
代理3:didReceiveData
一个NSData
类型的数据通常是由一系列不同的数据整合到一起得到的,不管是不是这样,请使用- enumerateByteRangesUsingBlock:
来遍历数据,而不是使用bytes
方法,因为bytes
缺少enumerateByteRangesUsingBlock
方法中的range
,有了range
,enumerateByteRangesUsingBlock
就可以对NSData
不同的数据块进行遍历,而不像bytes
把所有NSData
看成一个数据块。
这个方法和上面didCompleteWithError
算是NSUrlSession
的代理中最重要的两个方法了。
该代理方法可能会调用多次(比如分片获取数据),你需要自己实现该函数将所有数据整合在一起。
我们转发了这个方法到AF的代理AFURLSessionManagerTaskDelegate
中去,所以数据的拼接都是在AF的代理中进行的。这也是情理中的,毕竟每个响应数据都是对应各个task
,各个AF代理的。在AFURLSessionManager
只是做一些公共的处理。
//当接收到部分期望得到的数据(expected data)时,会调用该代理方法
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
// 调用的是AFURLSessionManagerTaskDelegate的didReceiveData方法
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
[delegate URLSession:session dataTask:dataTask didReceiveData:data];
if (self.dataTaskDidReceiveData) {
self.dataTaskDidReceiveData(session, dataTask, data);
}
}
代理4:willCacheResponse
当task
接收到所有期望的数据后,session
会调用此代理方法。如果你没有实现该方法,那么就会使用创建session
时使用的configuration
对象决定缓存策略。这个代理方法最初的目的是为了阻止缓存特定的URLs
或者修改NSCacheURLResponse
对象相关的userInfo
字典。
该方法只会当request
决定缓存response
时候调用。作为准则,responses
只会当以下条件都成立的时候返回缓存:
- 该
request
是HTTP
或HTTPS URL
的请求(或者你自定义的网络协议,并且确保该协议支持缓存) - 确保
request
请求是成功的(返回的status code
为200-299) - 返回的
response
是来自服务器端的,而非缓存中本身就有的 - 提供的
NSURLRequest
对象的缓存策略要允许进行缓存 - 服务器返回的
response
中与缓存相关的header
要允许缓存 - 该
response
的大小不能比提供的缓存空间大太多
// 询问data task或上传任务(upload task)是否缓存response
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
{
NSCachedURLResponse *cachedResponse = proposedResponse;
// 自定义方法,你可以什么都不做,返回原始的cachedResponse,或者使用修改后的cachedResponse
// 当然,你也可以返回NULL,这就意味着不需要缓存Response
if (self.dataTaskWillCacheResponse) {
cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse);
}
if (completionHandler) {
completionHandler(cachedResponse);
}
}
❹ NSURLSessionDownloadDelegate
代理1:didFinishDownloadingToURL
这个方法在下载完成的时候调用(必须实现),它也被转发到AF
自定义delegate
中,即AFURLSessionManagerTaskDelegate
。
//下载完成的时候调用(必须实现)
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
//这个是session的,也就是全局的,后面的个人代理也会做同样的这件事
if (self.downloadTaskDidFinishDownloading) {
// 自定义函数,根据从服务器端获取到的数据临时地址location等参数构建出你想要将临时文件移动的位置
NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (fileURL) {
// 如果fileURL存在的话,表示用户希望把临时数据存起来
delegate.downloadFileURL = fileURL;
NSError *error = nil;
// 将位于location位置的文件全部移到fileURL位置
[[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
// 如果移动文件失败,就发送AFURLSessionDownloadTaskDidFailToMoveFileNotification
if (error) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
}
return;
}
}
// 转发代理:这一步比较诡异,感觉有重复的嫌疑。或许是为了兼容以前代码吧
if (delegate) {
[delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
}
}
代理2:didFinishDownloadingToURL
-
bytesWritten
表示自上次调用该方法后,接收到的数据字节数 -
totalBytesWritten
表示目前已经接收到的数据字节数 -
totalBytesExpectedToWrite
表示期望收到的文件总字节数,是由Content-Length header
提供。如果没有提供,默认是NSURLSessionTransferSizeUnknown
。
//周期性地通知下载进度调用
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
if (self.downloadTaskDidWriteData) {
self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
}
代理3:didResumeAtOffset
如果一个resumable
下载任务被取消或者失败了,你可以请求一个resumeData
对象(比如在userInfo
字典中通过NSURLSessionDownloadTaskResumeData
这个键来获取到resumeData
),并使用它来提供足够的信息以重新开始下载任务。随后,你可以使用resumeData
作为downloadTaskWithResumeData:
或downloadTaskWithResumeData:completionHandler:
的参数。
当你调用这些方法时,你将开始一个新的下载任务。一旦你继续下载任务,session
会调用它的代理方法URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:
其中的downloadTask
参数表示的就是新的下载任务,这也意味着下载重新开始了。
如果文件缓存策略或者最后文件更新日期阻止重用已经存在的文件内容,那么fileOffset
该值为0,否则该值表示已经存在磁盘上的,不需要重新获取的数据,即当前已经下载data
的偏移量。
说白了,这就是断点续传啊!
// 告诉代理,下载任务重新开始下载了
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
if (self.downloadTaskDidResume) {
self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
}
}
至此NSUrlSesssion
的delegate
讲完了,好多啊啊啊啊啊啊,还是挺考验笔者和读者的耐心的。
大概总结下:
- 每个代理方法对应一个我们自定义的
Block
,如果Block
被赋值了,那么就调用它。 - 在这些代理方法里,我们做的处理都是相对于这个
sessionManager
所有的request
的,是公用的处理。 - 转发了3个代理方法到AF的
deleagate
中去了,AF中的deleagate
是需要对应每个task
去私有化处理的。
b、NSURLSessionDelegate转发到AF自定义的deleagate
Demo
Demo在我的Github上,欢迎下载。
SourceCodeAnalysisDemo