原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、功能模块
- 二、初始化方法
- 1、层层调用
- 2、最终调用的初始化方法
- 3、父类 AFURLSessionManager 的初始化方法
- 三、GET请求方法
- 1、生成 dataTask
- 2、生成request
- 3、观察 NSURLRequest 的属性
- 4、请求序列化
- 四、NSURLSessionDelegate的实现
- 1、代理之间的继承关系
- 2、NSURLSessionDelegate
- 3、NSURLSessionTaskDelegate
- 4、NSURLSessionDownloadDelegate
- Demo
- 参考文献
作为一个iOS开发者,也许你不知道NSURLRequest
、不知道NSURLSession
...(说不下去了...怎么会什么都不知道...)但是你一定知道AFNetworking
。大多数人习惯了只要是请求网络都用AF,但是你真的知道AF做了什么吗?为什么我们不用原生的NSURLSession
而选择AFNetworking
? 本文将从源码的角度去分析AF的实际作用。
一、功能模块
- 网络通信模块(
AFURLSessionManager
、AFHTTPSessionManger
) - 网络状态监听模块(
Reachability
) - 网络通信安全策略模块(
Security
) - 网络通信信息序列化/反序列化模块(
Serialization
) - 对于
iOS UIKit
库的扩展(UIKit
)
核心当然是网络通信模块AFURLSessionManager
。AF3.x是基于NSURLSession
来封装的。所以这个类围绕着NSURLSession
做了一系列的封装。而其余的四个模块,均是为了配合网络通信或对已有UIKit
的一个扩展工具包。
其中AFHTTPSessionManager
是继承于AFURLSessionManager
的,我们一般做网络请求都是用这个类,但是它本身是没有做实事的,只是做了一些简单的封装,把请求逻辑分发给父类AFURLSessionManager
或者其它类去做。
这五个模块所对应的类的结构关系图如下所示:
二、初始化方法
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
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];
}
2、最终调用的初始化方法
初始化方法调用父类的初始化方法,其父类也就是AF3.x最最核心的类AFURLSessionManager
。除此之外,方法中把baseURL
存了起来,还生成了一个请求序列对象和一个响应序列对象。
- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
...
return self;
}
❶ 调用父类初始化方法
self = [super initWithSessionConfiguration:configuration];
if (!self)
{
return nil;
}
❷ url有值且最后不包含/,那么在url的baseurlpath末尾添加/
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"])
{
url = [url URLByAppendingPathComponent:@"/"];
}
self.baseURL = url;
❸ 给requestSerializer、responseSerializer设置默认值
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
3、父类 AFURLSessionManager 的初始化方法
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration
{
self = [super init];
if (!self)
{
return nil;
}
.....
return self;
}
❶ 设置默认的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];
❻ 网络状态监听
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
❼ 设置存储词典。每一个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];
}
}];
二、GET请求方法
[manager GET:@"http://localhost" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
...
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
...
}];
在AFHTTPSessionManager
类中Get
请求方法会调用父类,也就是我们整个AF3.x的核心类AFURLSessionManager
的Get
请求方法,生成了一个系统的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
{
...
return dataTask;
}
1、生成 dataTask
a、在Get方法中生成一个dataTask,然后开始网络请求
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];
[dataTask resume];
b、生成 dataTask 的方法
- (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
{
......
return dataTask;
}
❶ 先调用AFHTTPRequestSerializer的requestWithMethod函数构建request。relativeToURL表示将URLString拼接到baseURL后面
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
❷ 处理request构建产生的错误。如果解析错误,直接返回
NSError *serializationError = nil;
if (serializationError)
{
if (failure)
{
...
}
return nil;
}
❸ diagnostic是诊断的意思,常与push pop搭配,来忽略一些编译器的警告,这里是用来忽略 ?带来的警告。completionQueue是我们自定义的一个GCD的Queue,如果设置了那么从这个Queue中回调结果,不存在则从主队列回调。
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
❹ 此时的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);
}
}
2、生成request
使用指定的HTTP method
和URLString
来构建一个NSMutableURLRequest
对象实例。如果method
是GET
、HEAD
、DELETE
,那parameter
将会被用来构建一个基于url
编码的查询字符串(query url
),并且这个字符串会直接加到request
的url
后面。对于其他的Method
,比如POST/PUT
,它们会根据parameterEncoding
属性进行编码,而后加到request
的http body
上。
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
......
return mutableRequest;
}
❶ 断言:debug模式下,如果该参数为nil,crash并直接打印出来
NSParameterAssert(method);
NSParameterAssert(URLString);
❷ 我们传进来的是一个字符串,在这里它帮你转成url
NSURL *url = [NSURL URLWithString:URLString];
❸ 设置请求方式(get、post、put.....)
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
❹ 将request的各种属性遍历。将观察到发生变化的属性添加到NSMutableURLRequest(如:timeout)。通过KVC动态的给mutableRequest添加value
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths())
{
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath])
{
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
❺ 将传入的parameters进行编码,拼接到url后并返回
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
3、观察 NSURLRequest 的属性
a、观察属性变化
这个c函数封装了一些NSURLRequest属性相关的方法名并将其作为数组返回
static NSArray * AFHTTPRequestSerializerObservedKeyPaths()
{
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths =
@[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}
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表示必须等receiver
收到先前的回复才能发送下个信息。
networkServiceType
设定request
的网络服务类型,默认是NSURLNetworkServiceTypeDefault
。这个网络服务是为了告诉系统网络层这个request
使用的目的,比如NSURLNetworkServiceTypeVoIP
表示这个request
是用来请求网际协议通话技术(Voice over IP
)。系统能根据提供的信息来优化网络处理,从而优化电池寿命,网络性能等等,客户端基本不使用。
timeoutInterval:超时机制,默认60秒
b、某个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];
}
}
.......
}
c、对应的KVO触发的方法
如果KVO
的触发机制是默认触发,则返回true
,否则返回false
。在这里,只要是AFHTTPRequestSerializerObservedKeyPaths
里面的属性,我们都取消自动触发kvo
机制,使用手动触发。为什么手动,我猜应该是为了在监听这些属性时可以用于某些特殊的操作,比如测试这些属性变化是否崩溃等。
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key])
{
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
当观察到这些set
方法被调用了,而且不为Null
就会添加到集合里,否则移除。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == AFHTTPRequestSerializerObserverContext)
{
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]])
{
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
}
else
{
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
4、请求序列化
举个例子演示下整个转码过程。
@{
@"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
目的是将原来的容器类型的参数变成字符串类型。
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSMutableURLRequest *mutableRequest = [request mutableCopy];
...
return mutableRequest;
}
a、设置请求方法
- 请求行(状态行):get、url、http协议
- 请求头:content-type、accept-language
- 请求体:get参数拼接在url后面,post数据放在body
❶ 从self.HTTPRequestHeaders里去遍历拿到设置的参数,如果request此项无值则给mutableRequest.headfiled赋值
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field])
{
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
❷ 将我们传入的字典转成字符串。至于转码方式,我们可以使用自定义的方式,也可以用AF默认的转码方式
NSString *query = nil;
if (parameters)
{
// 自定义的解析方式
if (self.queryStringSerialization)
{
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
}
// 默认解析方式 count=5&start=1
else
{
switch (self.queryStringSerializationStyle)
{
case AFHTTPRequestQueryStringDefaultStyle:
// 将parameters传入这个c函数
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
❸ 最后判断该request中是否包含了GET、HEAD、DELETE,因为这几个method的query是拼接到url后面的,而POST、PUT是把query拼接到http body中的。application/x-www-form-urlencoded是常用的表单发包方式,普通的表单提交,或者js发包,默认都是通过这种方式。
// get等请求
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]])
{
if (query && query.length > 0)
{
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
}
// post等请求
else
{
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"])
{
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
// 设置请求体
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
b、默认的解析方式
NSString * AFQueryStringFromParameters(NSDictionary *parameters)
{
// 一对
NSMutableArray *mutablePairs = [NSMutableArray array];
// 把参数传给AFQueryStringPairsFromDictionary
// AFQueryStringPair是数据处理类
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters))
{
// 百分号编码
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
// 将拆分数组进行拼接返回参数字符串
return [mutablePairs componentsJoinedByString:@"&"];
}
c、数据处理类
AFQueryStringPair
是一个数据处理类,只有两个属性:field
和value
。方法:URLEncodedStringValue
的作用是以key=value
的形式,用百分号编码。
@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;
- (NSString *)URLEncodedStringValue;
@end
- (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])];
}
}
d、请求参数对
❶ AFQueryStringPairsFromDictionary只是起过渡作用,往下继续调用。
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary)
{
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
❷ 在AFQueryStringPairsFromKeyAndValue中使用了递归,会对value的类型进行判断,有NSDictionary、NSArray、NSSet类型。
不过有人就会问了,在AFQueryStringPairsFromDictionary
中给AFQueryStringPairsFromKeyAndValue
函数传入的value
不是NSDictionary
嘛?还要判断那么多类型干啥?对,问得很好,这就是AFQueryStringPairsFromKeyAndValue
的核心——递归调用并解析,你不能保证NSDictionary
的value
中存放的不是一个NSArray
、NSSet
。
NSArray *AFQueryStringPairsFromKeyAndValue(NSString *key, id value)
{
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
...
return mutableQueryStringComponents;
}
❸ 根据需要排列的对象的description来进行升序排列。因为对象的description返回的是NSString,所以此处compare:使用的是NSString的compare函数。 比如:@[@"foo", @"bar", @"bae"] ----> @[@"bae", @"bar",@"foo"]
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
❹ 判断vaLue是什么类型的,然后去递归调用自己,直到解析的是除了array、dictionary、set以外的元素,然后把得到的参数数组返回
if ([value isKindOfClass:[NSDictionary class]])
{
NSDictionary *dictionary = value;
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]])
{
id nestedValue = dictionary[nestedKey];
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)];
}
}
既然是递归,那么就要有结束递归的情况,比如解析到最后,对应value
是一个NSString
,那么就得调用函数中最后的else
语句。
else
{
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
四、NSURLSession相关的代理
1、代理之间的继承关系
在第一步探索父类AFURLSessionManager
的初始化方法时,有句代码把AFURLSessionManager
作为了所有的task
的delegate
。
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration
{
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
这些代理中的方法,如果响应了就会去调用。每个代理方法对应一个我们自定义的Block
,如果Block
被赋值了,那么就调用它。
2、NSURLSessionDelegate
a、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];
}
b、didReceiveChallenge:收到服务端的challenge,例如https需要验证证书等
web
服务器接收到客户端请求时,有时候需要先验证客户端是否为正常用户,再决定是够返回真实数据。这种情况称之为服务端要求客户端接收挑战。接收到挑战后,客户端要根据服务端传来的challenge
来生成completionHandler
所需的NSURLSessionAuthChallengeDisposition
和NSURLCredential
。disposition
指定应对这个挑战的方法,而credential
是客户端生成的挑战证书,注意只有challenge
中认证方法为NSURLAuthenticationMethodServerTrust
的时候,才需要生成挑战证书。最后调用completionHandler
回应服务器端的挑战。
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
...
}
❶ 设置挑战处理类型为默认
- NSURLSessionAuthChallengeUseCredential:使用指定的证书
- NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
- NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战
- NSURLSessionAuthChallengeRejectProtectionSpace:拒绝此挑战,并尝试下一个验证保护空间
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
❷ 自定义方法,用来应对服务器端的认证挑战
__block NSURLCredential *credential = nil;// 证书
if (self.sessionDidReceiveAuthenticationChallenge)
{
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
}
❸ 倘若没有自定义Block
// 判断接收服务器挑战的方法是否是信任证书
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
// 只需要验证服务端证书是否安全,即https的单向认证,这是AF默认处理的认证方式
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host])
{
// 信任评估通过,就从受保护空间里面拿出证书,回调给服务器,告诉服务器,我信任你,你给我发送数据吧
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
// 确定挑战的方式
if (credential)
{
disposition = NSURLSessionAuthChallengeUseCredential;// 证书挑战
}
else
{
disposition = NSURLSessionAuthChallengePerformDefaultHandling;// 默认挑战
}
}
else
{
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;// 取消挑战
}
}
// 默认挑战方式
else
{
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
❹ 完成挑战,将信任凭证发送给服务端
if (completionHandler)
{
completionHandler(disposition, credential);
}
4、NSURLSessionDownloadDelegate
a、didFinishDownloadingToURL
- (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];
}
}
b、didFinishDownloadingToURL:周期性地通知下载进度调用
- bytesWritten:表示自上次调用该方法后,接收到的数据字节数
- totalBytesWritten:表示目前已经接收到的数据字节数
- totalBytesExpectedToWrite:表示期望收到的文件总字节数
- (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);
}
}
c、didResumeAtOffset:下载任务重新开始下载
如果一个resumable
下载任务被取消或者失败了,你可以请求一个resumeData
对象(比如在userInfo
字典中通过NSURLSessionDownloadTaskResumeData
这个键来获取到resumeData
),并使用它来提供足够的信息以重新开始下载任务。
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
if (self.downloadTaskDidResume)
{
self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
}
}
Demo
Demo在我的Github上,欢迎下载。
SourceCodeAnalysisDemo