AFN 3.0学习总结(五)

参考: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
image

通过这个宏定义,我们可以联想到判断iOS版本号的几个方法

1、[UIDevice currentDevice].systemVersion

2、通过比较Foundation中的宏定义

3、通过在某系版本中新出现的方法来判断,UIAlertController 这个类是iOS8之后才出现的 NS_CLASS_AVAILABLE_IOS(8_0),如果当前系统版本没有这个类NSClassFromString(@"UIAlertController" == (null),从而判断当前版本是否大于等于iOS8

image

AFN中所有的和创建任务相关的事件都放到了一个单例队列中,而且是串行的。

image

从名字就可以看出,这是一个安全的创建任务的方法

知识点:dispatch_block_t

系统是这么定义的

typedef void (^dispatch_block_t)(void);

关于这个block我们要注意以下几点:

1、MRC情况下,使用完要记得release

2、生命block时,它是被分配到栈上的,使用时,我们需要把block拷贝到堆上面,因为栈是系统管理的,随时可能被释放。

image

这个方法是用来创建一个队列来管理数据的处理,同上面的方法类似,不过这个创建的是并发的,加快的数据处理的速度。

AFURLSessionManagerTaskDelegate

这个代理的目的:

1、处理上传或者下载的进度

2、处理获取完数据后的行为

image

需要说一下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这个属性来获取进度

image

移除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启动时加载所有类的时候调用,且只会调用一次,所以这就有了使用场景了,当想使用运行时做一些事情的时候,就能够用上这个方法了

举几个使用这个方法的例子:

下边就看看代码部分:

//根据两个方法名称交换两个方法,内部实现是先根据函数名获取到对应方法实现
//在调用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建立联系

image.png

上面这个函数中有这么一个操作

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" 不去重

image.png
我们注意到上面方法中使用了_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:`方法。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容