参考:AFNetworking 3.0 源码解读(五)之 AFURLSessionManager
说明:很多内容都是摘抄原文,只是根据自己的需要进行摘抄或者总结,如有不妥请及时指出,谢谢。
这次主要总结AFURLSessionManager,下一篇会总结 AFHTTPSessionManager ,它是AFURLSessionManager的一个子类。
其实AFURLSessionManager创建并管理着NSURLSession这个对象,而NSURLSession又基于NSURLSessionConfiguration。
AFURLSessionManager实现了4个协议
1、NSURLSessionDelegate
URLSession:didBecomeInvalidWithError:
URLSession:didReceiveChallenge:completionHandler:
URLSessionDidFinishEventsForBackgroundURLSession:
2、NSURLSessionTaskDelegate
URLSession:willPerformHTTPRedirection:newRequest:completionHandler:
URLSession:task:didReceiveChallenge:completionHandler:
URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:
URLSession:task:needNewBodyStream:
URLSession:task:didCompleteWithError:
3、NSURLSessionDataDelegate
URLSession:dataTask:didReceiveResponse:completionHandler:
URLSession:dataTask:didBecomeDownloadTask:
URLSession:dataTask:didReceiveData:
URLSession:dataTask:willCacheResponse:completionHandler:
4、NSURLSessionDownloadDelegate
URLSession:downloadTask:didFinishDownloadingToURL:
URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:
URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:
- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
这个方法是指定初始化方法,什么叫指定初始化方法?
NS_DESIGNATED_INITIALIZER
这个宏告诉开发者,如果写一个继承A类的子类B,那么就要调用父类A的指定的初始化方法。举个例子:
@interface MyClass : NSObject @property(copy, nonatomic) NSString *name;
-(instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER;
-(instancetype)init;
如果定义一个ClassB : MyClass,那么ClassB中就必须实现-(instancetype)initWithName:(NSString *)name这个方法,否则就会收到警告。
/** Invalidates the managed session, optionally canceling pending tasks. @param cancelPendingTasks Whether or not to cancel pending tasks.
根据是否取消未完成的任务来使session失效
*/
- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks;
NSURLSession有两个方法:
1、-(void)finishTasksAndInvalidate; 标示待完成所有的任务后失效
2、-(void)invalidateAndCancel; 标示 立即失效,未完成的任务也将结束
接下来看.m中的内容
#ifndef NSFoundationVersionNumber_iOS_8_0
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug 1140.11
#else
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0
通过这个宏定义,我们可以联想到判断iOS版本号的几个方法
1、[UIDevice currentDevice].systemVersion
2、通过比较Foundation中的宏定义
3、通过在某系版本中新出现的方法来判断,UIAlertController 这个类是iOS8之后才出现的 NS_CLASS_AVAILABLE_IOS(8_0),如果当前系统版本没有这个类NSClassFromString(@"UIAlertController" == (null),从而判断当前版本是否大于等于iOS8
AFN中所有的和创建任务相关的事件都放到了一个单例队列中,而且是串行的。
从名字就可以看出,这是一个安全的创建任务的方法
知识点:dispatch_block_t
系统是这么定义的
typedef void (^dispatch_block_t)(void);
关于这个block我们要注意以下几点:
1、MRC情况下,使用完要记得release
2、生命block时,它是被分配到栈上的,使用时,我们需要把block拷贝到堆上面,因为栈是系统管理的,随时可能被释放。
这个方法是用来创建一个队列来管理数据的处理,同上面的方法类似,不过这个创建的是并发的,加快的数据处理的速度。
AFURLSessionManagerTaskDelegate
这个代理的目的:
1、处理上传或者下载的进度
2、处理获取完数据后的行为
需要说一下NSProgress,它内部是使用kvo实现的,我们应该尽量向这个类靠拢。
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
//We no longer need the reference, so nil it out to gain back some memory.
self.mutableData = nil;
}
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
if (error) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
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;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
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];
});
});
});
}
}
NSProgress 通过监听fractionCompleted这个属性来获取进度
移除kvo以及监听对象
在这里要说一下关于task四个代理的调用问题。
task一共有4个delegate,只要设置了一个,就代表四个全部设置,有时候一些delegate不会被触发的原因在于这四种delegate是针对不同的URLSession类型和URLSessionTask类型来进行响应的,也就是说不同的类型只会触发这些delegate中的一部分,而不是触发所有的delegate。
举例如下:
1、触发NSURLSessionDataDelegate
//使用函数dataTask来接收数据
-(void)URLSession:(NSURLSession *)session dataTask:
(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
//则NSURLSession部分的代码如下
NSURLSessionConfiguration* ephConfiguration=[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession* session=[NSURLSession sessionWithConfiguration:ephConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURL* url=[NSURL URLWithString:@"http://www.example.com/external_links/01.png"];
NSURLSessionDataTask* dataTask=[session dataTaskWithURL:url];
[dataTask resume];
2、触发NSURLSessionDownloadDelegate
//使用函数downloadTask来接受数据
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
//则NSURLSession部分的代码如下
NSURLSessionConfiguration* ephConfiguration=[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession* session=[NSURLSession sessionWithConfiguration:ephConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURL* url=[NSURL URLWithString:@"http://www.example.com/external_links/01.png"];
NSURLSessionDownloadTask* dataTask=[session downloadTaskWithURL:url];
[dataTask resume];
这两段代码的主要区别在于NSURLSessionTask的类型的不同,造成了不同的Delegate被触发.
#pragma mark - NSURLSessionDataTaskDelegate
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
[self.mutableData appendData:data];
}
--
#pragma mark - NSURLSessionTaskDelegate
(void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
//We no longer need the reference, so nil it out to gain back some memory.
self.mutableData = nil;
}
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
if (error) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
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;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
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];
});
});
});
}
}
这个方法是获取数据完成了方法。最终通过self.completionHandler和** [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];**这两个手段来传递数据和事件
我们主要看看这个通知的userinfo会有那些信息:
AFNetworkingTaskDidCompleteResponseSerializerKey -> manager.responseSerializer
AFNetworkingTaskDidCompleteAssetPathKey -> self.downloadFileURL
AFNetworkingTaskDidCompleteResponseDataKey -> data
AFNetworkingTaskDidCompleteErrorKey -> error
AFNetworkingTaskDidCompleteSerializedResponseKey -> responseObject
#pragma mark - NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
self.downloadFileURL = nil;
if (self.downloadTaskDidFinishDownloading) {
self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
if (self.downloadFileURL) {
NSError *fileManagerError = nil;
if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
}
}
}
}
这个方法在下载完成后会调用。之前有一个使用场景,就是视频边下载边播放。要求在视频在下载完之前拿到正在下载的数据。ASI有一个属性能够拿到fileURL,AFNetworking却没有这个属性,现在看来,通过设置
- (void)setDownloadTaskDidFinishDownloadingBlock:(NSURL * (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block {
self.downloadTaskDidFinishDownloading = block;
}
这样可以把数据写入我们指定的地方
_AFURLSessionTaskSwizzling
当时看这个私有类的时候一直想不通为什么要弄一个这样的类呢?首先看了AFNetworking给出的解释https://github.com/AFNetworking/AFNetworking/pull/2702 大概说了当初这个私有类的由来,ios7和ios8 task的父类并不一样,关键是resume
and suspend
这两个方法的调用。
因此,AFNetworking 利用Runtime交换了resume
and suspend
的方法实现。在替换的方法中发送了状态的通知。这个通知被使用在UIActivityIndicatorView+AFNetworking
这个UIActivityIndicatorView的分类中。
还有值得说的是 + (void)load这个方法,这个方法会在app启动时加载所有类的时候调用,且只会调用一次,所以这就有了使用场景了,当想使用运行时做一些事情的时候,就能够用上这个方法了
举几个使用这个方法的例子:
- SDAutoLayout
- IQKeyBoardManager
- UITableView+FDTemplateLayoutCell
- MJExtension
下边就看看代码部分:
//根据两个方法名称交换两个方法,内部实现是先根据函数名获取到对应方法实现
//在调用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) {
return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));
}
--
+ (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添加一个名称为@selector(af_resume)的方法,方法实现为afResumeMethod
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
}
//同上
if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
}
}
--
- (void)af_resume {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
//经过method swizzling之后,这里调用af_resume不会引起死循环,因为相当于调用的是系统的resume
[self af_resume];
//如果state是其它状态,则发出通知改编成resume状态
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];
}
}
--
+ (void)load {
/**
WARNING: Trouble Ahead
https://github.com/AFNetworking/AFNetworking/pull/2702
*/
if (NSClassFromString(@"NSURLSessionTask")) {
/**
iOS 7 and iOS 8 differ in NSURLSessionTask implementation, which makes the next bit of code a bit tricky.
Many Unit Tests have been built to validate as much of this behavior has possible.
Here is what we know:
- NSURLSessionTasks are implemented with class clusters, meaning the class you request from the API isn't actually the type of class you will get back.
- Simply referencing `[NSURLSessionTask class]` will not work. You need to ask an `NSURLSession` to actually create an object, and grab the class from there.
- On iOS 7, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `__NSCFURLSessionTask`.
- On iOS 8, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `NSURLSessionTask`.
- On iOS 7, `__NSCFLocalSessionTask` and `__NSCFURLSessionTask` are the only two classes that have their own implementations of `resume` and `suspend`, and `__NSCFLocalSessionTask` DOES NOT CALL SUPER. This means both classes need to be swizzled.
- On iOS 8, `NSURLSessionTask` is the only class that implements `resume` and `suspend`. This means this is the only class that needs to be swizzled.
- Because `NSURLSessionTask` is not involved in the class hierarchy for every version of iOS, its easier to add the swizzled methods to a dummy class and manage them there.
Some Assumptions:
- No implementations of `resume` or `suspend` call super. If this were to change in a future version of iOS, we'd need to handle it.
- No background task classes override `resume` or `suspend`
The current solution:
1) Grab an instance of `__NSCFLocalDataTask` by asking an instance of `NSURLSession` for a data task.
2) Grab a pointer to the original implementation of `af_resume`
3) Check to see if the current class has an implementation of resume. If so, continue to step 4.
4) Grab the super class of the current class.
5) Grab a pointer for the current class to the current implementation of `resume`.
6) Grab a pointer for the super class to the current implementation of `resume`.
7) If the current class implementation of `resume` is not equal to the super class implementation of `resume` AND the current implementation of `resume` is not equal to the original implementation of `af_resume`, THEN swizzle the methods
8) Set the current class to the super class, and repeat steps 3-8
翻译:iOS7和iOS8在NSURLSessionTask实现上有些不同,这使得下面的实现略显复杂
关于这个问题,大家做了很多Uint Test,足以证明这个方法是可行的
目前我们所知:
- NSURLSessionTasks是一组class的统称,如果你仅仅使用系统提供的api来获取NSURLSessionTasks的class,并不一定返回的是你想要的那个(获取NSURLSessionTask的class目的是为了获取其resume方法)
-简单的使用[NSURLSessionTask class]并不起作用,你需要新建一个NSURLSession,并根据创建的session再构建出一个NSURLSessionTask对象才行。
-iOS7上localDataTask(下面代码构造出的NSURLSessionDataTask类型的变量,为了获取对应Class)的类型是 __NSCFLocalDataTask,__NSCFLocalDataTask继承自__NSCFLocalSessionTask。
-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、检查当前类是否实现了af_resume,如果实现了,继续第4步
while (class_getInstanceMethod(currentClass, @selector(resume))) {
//4、获取当前class的父类,super class
Class superClass = [currentClass superclass];
//5、获取当前类对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];
}
}
AFURLSessionManager
- (instancetype)initWithSessionConfiguration:
(NSURLSessionConfiguration *)configuration
从初始化函数中可以看出
1、创建的NSOperationQueue且并发数为1
2、responseSerializer默认为json格式
3、securityPolicy默认为defaultPolicy
4、reachabilityManager用于监控网络状态
- (NSString *)taskDescriptionForSessionTasks {
return [NSString stringWithFormat:@"%p", self];
}
- (void)taskDidResume:(NSNotification *)notification {
NSURLSessionTask *task = notification.object;
if ([task respondsToSelector:@selector(taskDescription)]) {
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];
});
}
}
}
taskDescriptionForSessionTasks这个方法的作用是之后判断消息是不是来源于AFN。AFN在添加每个任务的时候,都会给task的taskDescription赋值为self.taskDescriptionForSessionTasks。
在上面的taskDidResume可以看到,这里判断了一下taskDescription是否一致。
为什么这块要这么处理?解释一下:
因为前面对resume函数进行了swizzling,而af_resume里面post了一个名为AFNSURLSessionTaskDidResumeNotification的消息,但是如果用户采用的不是AFN调用的af_resume,其实也会发这个消息,所以我们要进行过滤。
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
这两个方法是把task和delegate进行关联
- (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] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
[self setDelegate:delegate forTask:dataTask];
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
给datatask添加delegate,AFN中的每一个task肯定都有一个delegate。根据这个方法,我们可以给出task添加代理的步骤:
1、新建AFURLSessionManagerTaskDelegate
2、设置delegate
3、设置dataTask.taskDescription
4、task、delegate、AFURLSessionManager建立联系
上面这个函数中有这么一个操作
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
这么写是什么意思呢?测试如下:
- (void)testUnion {
NSArray *array1 = [NSArray arrayWithObjects:@1, @2, @1, @3, nil];
NSArray *array2 = [NSArray arrayWithObjects:@3, @1, @3, @5, nil];
NSArray *result1 = [@[array1, array2] valueForKeyPath:@"@distinctUnionOfArrays.self"];
NSLog(@"%@", result1);
NSArray *result2 = [@[array1, array2] valueForKeyPath:@"@unionOfArrays.self"];
NSLog(@"%@", result2);
NSArray *result3 = [@[array1, array2] valueForKeyPath:@"@distinctUnionOfObjects.self"];
NSLog(@"%@", result3);
NSArray *result4 = [@[array1, array2] valueForKeyPath:@"@unionOfObjects.self"];
NSLog(@"%@", result4);
NSArray *result5 = [array1 valueForKeyPath:@"@unionOfObjects.self"];
NSLog(@"%@", result5);
NSArray *result6 = [array1 valueForKeyPath:@"@distinctUnionOfObjects.self"];
NSLog(@"%@", result6);
}
输出结果:
2018-01-13 21:26:49.351257+0800 test[41247:3156074] (
5,
1,
2,
3
)
2018-01-13 21:26:49.351524+0800 test[41247:3156074] (
1,
2,
1,
3,
3,
1,
3,
5
)
2018-01-13 21:26:49.351806+0800 test[41247:3156074] (
(
1,
2,
1,
3
),
(
3,
1,
3,
5
)
)
2018-01-13 21:26:49.351978+0800 test[41247:3156074] (
(
1,
2,
1,
3
),
(
3,
1,
3,
5
)
)
2018-01-13 21:26:49.352253+0800 test[41247:3156074] (
1,
2,
1,
3
)
2018-01-13 21:26:52.527501+0800 test[41247:3156074] (
3,
2,
1
)
从结果中可以得出如下结论:
1、@distinctUnionOfArrays.self 数组合并,且内部去重
2、@unionOfArrays.self 数组合并,不去重
3、@distinctUnionOfObjects.self、@unionOfObjects.self应用于数组时无效,数组也不会合并
4、@distinctUnionOfObjects.self 单个数组去重
5、@"@unionOfObjects.self" 不去重
_cmd
当作参数。
在oc中,当方法被编译器转换成objc_msgSend函数后,除了方法必须的参数,objc_msgSend还会接收两个特殊的参数:receiver 与 selector。
objc_msgSend(receiver, selector, arg1, arg2, ...)
receiver 表示当前方法调用的类实例对象。
selector则表示当前方法所对应的selector。
这两个参数是编译器自动填充的,我们在调用方法时,不必在源代码中显示传入,因此可以被看做是“隐式参数”。
如果想要在source code中获取这两个参数,则可以用self(当前类实例对象)和_cmd(当前调用方法的selector)来表示。
举个例子:
- (void)testCmd {
NSLog(@"%@ %@", [self class], NSStringFromSelector(_cmd));
}
打印出如下结果:
2018-01-13 21:34:37.574305+0800 test[41300:3165057] ViewController testCmd
NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
//创建默认的处理方式,PerformDefaultHandling方式将忽略Credential这个参数
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
//调动自身的处理方法,也就是说我们通过sessionDidReceiveAuthenticationChallenge这个block接收session,challenge 参数,返回一个NSURLSessionAuthChallengeDisposition结果,这个业务使我们自己在这个block中完成。
if (self.sessionDidReceiveAuthenticationChallenge) {
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {//如果没有实现自定义的验证过程
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//使用安全策略来验证
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
//如果验证通过,根据serverTrust创建依据
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
//有的话就返回UseCredential
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {//没有验证通过,返回NSURLSessionAuthChallengeCancelAuthenticationChallenge
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
这个代理方法会在下面两种情况被调用
1、当远程服务器要求客户端提供证书或者Windows NT LAN Manager (NTLM)验证
2、当session初次和服务器通过SSL或TSL建立连接,客户端需要验证服务端证书链
如果没有实现这个方法,session就会调用delegate的URLSession:task:didReceiveChallenge:completionHandler:方法。
如果challenge.protectionSpace.authenticationMethod 在下边4个中时,才会调用
* NSURLAuthenticationMethodNTLM
* NSURLAuthenticationMethodNegotiate 是否使用 Kerberos or NTLM 验证
* NSURLAuthenticationMethodClientCertificate
* NSURLAuthenticationMethodServerTrust
否则调用`URLSession:task:didReceiveChallenge:completionHandler:`方法。