【源码解读】AFNetWorking ─── 网络请求

一. 前言

AFNetWorking的使用率非常高,这个不必多说。无论是AFNetWorking还是SDWebImage等涉及到网络请求的,底层都是封装了原生的网络请求。如果对原生的网络请求还不熟悉的话,可以看看我之前写的这篇文章【iOS小结】NSURLSession
AFNetWorking的功能很多,我这篇主要解读的是网络请求(关于AFNetWorking怎么封装网络请求的知识)。

二. AFNetWorking的网络请求

网络请求涉及的类主要是这两个:

  • AFHTTPSessionManager:拥有生成任务,管理任务的功能,并提供HTTP方法的API接口
  • AFURLSessionManager:封装了生成任务,管理任务
网络请求部分.png

我们先来看一个简单的POST请求,我们只需把urlStringparameters传进去,AFHTTPSessionManager就会创建一个POST的请求任务,并把请求进度,请求成功,请求失败通过Block回调给你。
简单来说,其内部的实现其实是会通过这两个参数生成对应的NSURLRequest,并通过NSURLSession创建请求任务,遵守相关的NSURLSession的协议,在对应的协议方法里面进行判断处理,回调。

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//设置URL
NSString *urlString = @"www.baidu.com/login/info";
//设置参数
NSDictionary *parameters = @{@"username":@"Tom",@"pwd":@"123"};
//发送post请求
[manager POST:urlString parameters:parameters progress:^(NSProgress * _Nonnull uploadProgress) {
    //上传过程中的回调
} success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    //成功的回调
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    //失败的回调
}];

接下来我们来一步步分析:

AFHTTPSessionManager

当使用AFNetWorking进行网络请求时,我们会创建AFHTTPSessionManager对象,进行对应的HTTP请求。AFHTTPSessionManager继承于AFURLSessionManager,除了AFURLSessionManager的功能外,还提供HTTP方法的接口。
以下红框是AFHTTPSessionManager提供的方法,以及对应的用途:


HTTP支持的方法.png

我们再来看具体提供的方法:

//GET方法
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(nullable id)parameters
                      success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                      failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
//GET方法(带下载进度)
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
                            parameters:(nullable id)parameters
                              progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
                               success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                               failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

//HEAD方法
- (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString
                    parameters:(nullable id)parameters
                       success:(nullable void (^)(NSURLSessionDataTask *task))success
                       failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

//POST方法
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(nullable id)parameters
                       success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                       failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
//POST方法(带上传进度)
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
//POST方法(带构造体Block)→ 用于Multipart/form-data
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(nullable id)parameters
     constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
                       success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                       failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE;
//POST方法(带构造体Block和上传进度)→ 用于Multipart/form-data
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
              constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

//PUT方法
- (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString
                   parameters:(nullable id)parameters
                      success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                      failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

//PATCH方法
- (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString
                     parameters:(nullable id)parameters
                        success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                        failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

//DELETE方法
- (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString
                      parameters:(nullable id)parameters
                         success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                         failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

其中比较特殊的是POST方法,POST带构造体Block的方法是用于Multipart/form-data,用于上传文件。具体原理我就不多解释,可以看看这个例子AFNetworking文件上传Multipart/form-data POST文件上传详解

另外,这两种方法创建的是uploadTask(通过文件流上传)。在下面方法中通过请求序列化对象self.requestSerializer将url、参数和构造体Block序列化成对应的request,然后通过该request去创建对应的uploadTask。

// POST请求(带构造体block,带进度,用于Multipart/form-data,上传)
// 用于Multipart/form-data上传的基础方法
- (NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(id)parameters
     constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                      progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgress
                       success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                       failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
    NSError *serializationError = nil;
    //序列化request(构建)
    NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
    //判断序列化是否成功
    if (serializationError) {
        //失败的话就通过failure回调,返回nil
        if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic pop
        }

        return nil;
    }

    //创建uploadTask(通过文件流上传文件)
    __block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:uploadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(task, error);
            }
        } else {
            if (success) {
                success(task, responseObject);
            }
        }
    }];
    [task resume];
    return task;
}

而其他方法创建的是dataTask。也是通过请求序列化对象self.requestSerializer将url、参数序列化成对应的request,然后通过该request去创建对应的dataTask。

// 所有请求的基础方法(通过改变method参数)
- (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
{
    NSError *serializationError = nil;
    //把参数,还有各种东西转换为一个request
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    
    //转换错误就在failure中回调
    if (serializationError) {
        if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            //如果解析错误,直接返回
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
#pragma clang diagnostic pop
        }

        return nil;
    }

    //生成一个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;
}

总之,AFHTTPSessionManager封装了几种我们常用的网络请求以及文件上传的接口(当然也是不全的,比如少了downloadTask,这边就需要我们直接使用AFHTTPRequestSerializer去创建)。
通过AFHTTPRequestSerializer对象通过我们传入的参数转化成对应的NSMutableURLRequest。然后通过request调用其父类AFURLSessionManager创建对应的task。当然也把对应的success和failure等回调传进去。

另外,AFHTTPSessionManager有三个属性在初始化时会被创建,baseURL好理解,requestSerializer是用来序列化(构建)request的,
responseSerializer是用来处理响应数据的。requestSerializer是AFHTTPSessionManager特有的属性,因为AFHTTPSessionManager赋值构建request,然后生成对应的task,而responseSerializer实际上是用在AFURLSessionManager,因为它那边负责生成管理任务,并且对结果进行处理。

self.baseURL = url;
// 设置请求和响应序列化对象
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
AFURLSessionManager

其实从AFHTTPSessionManager先分析,再分析AFURLSessionManager是有弊端的,因为AFHTTPSessionManager继承于AFURLSessionManager,所以AFHTTPSessionManager的很多功能只有留到讲解AFURLSessionManager的时候才能说明。

之前AFHTTPSessionManager的分析说过,AFURLSessionManager封装了生成、管理任务。AFHTTPSessionManager只需要生成对应request,通过request调用AFURLSessionManager生成task的方法。
AFURLSessionManager是一个怎么样的类呢?
这边的探究分成两部分:
① 提供什么功能(其实还是跟任务有关)→看.h
② 怎么实现这些功能 →看.m

① AFURLSessionManager提供的功能

AFURLSessionManager提供的功能也是之前AFHTTPSessionManager未讲到的功能。AFURLSessionManager主要是封装了生成任务和管理任务,AFURLSessionManager实际上是NSURLSession对象的组合体,提供的功能自然也是围绕着任务。
AFURLSessionManager提供的功能主要有三种:
1)获取正在执行的所有任务、请求任务、上传任务、下载任务。

@property (readonly, nonatomic, strong) NSArray <NSURLSessionTask *> *tasks;
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDataTask *> *dataTasks;
@property (readonly, nonatomic, strong) NSArray <NSURLSessionUploadTask *> *uploadTasks;
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDownloadTask *> *downloadTasks;

2)提供多种创建task的方法(封装了所有NSURLSession创建task的方法)
3)提供了对NSURLSession协议的自定义处理的Block。(类似于代理)

② AFURLSessionManager怎么实现这些功能

AFURLSessionManager.m的内容其实挺多的,分开来看其实主要有四部分内容(最主要还是第四部分):
1)单例创建各种队列的C函数。定义各种key,Notification的值。定义各种Block。(该部分也好理解,就是为了让下面来使用)
2)_AFURLSessionTaskSwizzling(主要用来交换NSURLSessionTask的Resume和Suspend方法)→主要是为了添加通知的代码?

+ (void)load {
    /**
     WARNING: 高能预警
     https://github.com/AFNetworking/AFNetworking/pull/2702
     */
    // 担心以后iOS中不存在NSURLSessionTask
    if (NSClassFromString(@"NSURLSessionTask")) {
        /**
         iOS 7和iOS 8在NSURLSessionTask实现上有些许不同,这使得下面的代码实现略显trick
         关于这个问题,大家做了很多Unit Test,足以证明这个方法是可行的
         目前我们所知的:
            - NSURLSessionTasks是一组class的统称,如果你仅仅使用提供的API来获取NSURLSessionTask的class,并不一定返回的是你想要的那个(获取NSURLSessionTask的class目的是为了获取其resume方法)
            - 简单地使用[NSURLSessionTask class]并不起作用。你需要新建一个NSURLSession,并根据创建的session再构建出一个NSURLSessionTask对象才行。
            - iOS 7上,localDataTask(下面代码构造出的NSURLSessionDataTask类型的变量,为了获取对应Class)的类型是 __NSCFLocalDataTask,__NSCFLocalDataTask继承自__NSCFLocalSessionTask,__NSCFLocalSessionTask继承自__NSCFURLSessionTask。
            - iOS 8上,localDataTask的类型为__NSCFLocalDataTask,__NSCFLocalDataTask继承自__NSCFLocalSessionTask,__NSCFLocalSessionTask继承自NSURLSessionTask
          - iOS 7上,__NSCFLocalSessionTask和__NSCFURLSessionTask是仅有的两个实现了resume和suspend方法的类,另外__NSCFLocalSessionTask中的resume和suspend并没有调用其父类(即__NSCFURLSessionTask)方法,这也意味着两个类的方法都需要进行method swizzling。
            - iOS 8上,NSURLSessionTask是唯一实现了resume和suspend方法的类。这也意味着其是唯一需要进行method swizzling的类
            - 因为NSURLSessionTask并不是在每个iOS版本中都存在,所以把这些放在此处(即load函数中),比如给一个dummy class添加swizzled方法都会变得很方便,管理起来也方便。
        
         一些假设前提:
            - 目前iOS中resume和suspend的方法实现中并没有调用对应的父类方法。如果日后iOS改变了这种做法,我们还需要重新处理
            - 没有哪个后台task会重写resume和suspend函数
         
         */
        // 1) 首先构建一个NSURLSession对象session,再通过session构建出一个_NSCFLocalDataTask变量
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
        NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
        NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
        // 2) 获取到af_resume实现的指针
        IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
        Class currentClass = [localDataTask class];
        // 3) 检查当前class是否实现了resume。如果实现了,继续第4步。
        while (class_getInstanceMethod(currentClass, @selector(resume))) {
            // 4) 获取到当前class的父类(superClass)
            Class superClass = [currentClass superclass];
            // 5) 获取到当前class对于resume实现的指针
            IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
            //  6) 获取到父类对于resume实现的指针
            IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
            // 7) 如果当前class对于resume的实现和父类不一样(类似iOS7上的情况),并且当前class的resume实现和af_resume不一样,才进行method swizzling。
            if (classResumeIMP != superclassResumeIMP &&
                originalAFResumeIMP != classResumeIMP) {
                [self swizzleResumeAndSuspendMethodForClass:currentClass];
            }
            // 8) 设置当前操作的class为其父类class,重复步骤3~8
            currentClass = [currentClass superclass];
        }
        
        [localDataTask cancel];
        [session finishTasksAndInvalidate];
    }
}

+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
    Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
    Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));

    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));
    }
}

- (NSURLSessionTaskState)state {
    //在实际的虚拟类中不应调用状态方法。
    NSAssert(NO, @"State method should never be called in the actual dummy class");
    // 初始状态是NSURLSessionTaskStateCanceling;
    return NSURLSessionTaskStateCanceling;
}

- (void)af_resume {
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    NSURLSessionTaskState state = [self state];
    //执行resume方法
    [self af_resume];
    
    //如果之前是其他状态,就变回resume状态,发出DidResume的通知
    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];
    //执行suspend方法
    [self af_suspend];
    
    //如果之前是其他状态,就变回suspend状态,发出DidSuspend的通知
    if (state != NSURLSessionTaskStateSuspended) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
    }
}

3)AFURLSessionManagerTaskDelegate(task代理类)
为什么是task的代理类呢?主要是因为task将它将处理获取数据,监听进度的职责放在该类去执行,AFURLSessionManagerTaskDelegate是task的代理人。我们先来看AFURLSessionManager里面的方法:(同理对应的uploadTask和downloadTask也有设置的方法)

//设置普通请求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;
    //为task设置代理(把dataTask传给delegate)
    [self setDelegate:delegate forTask:dataTask];

    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

可以说task该处理的回调都放在代理类这边处理了。我们再看看这个代理类怎么处理数据和监听进度。

关于进度的监听,AFURLSessionManagerTaskDelegate设置了两个NSProgress对象(uploadProgress和downloadProgress)来分别监听上传和下载进度。(关于NSProgress的作用可以看这篇文章进度: NSProgress
简单说起来,NSProgress是一个记录进度的类,在需要记录多个进度有很大的优势。uploadProgress和downloadProgress除了记录进度外,通过设置也可以控制task的生命周期。
另外,通过对task接收到的字节数、期望接收到的字节数、发送的字节数、期望发送的字节数进行监听,值改变时去设置uploadProgress或downloadProgress对应的总值或完成值。通过对uploadProgress和downloadProgress完成度的监听,通过对应的uploadProgressBlock和downloadProgressBlock将进度信息回调出去。

//为task设置进度(task的进度会受到监听)
- (void)setupProgressForTask:(NSURLSessionTask *)task {
    __weak __typeof__(task) weakTask = task;

    //设置上传和下载进度条的总数目,暂停,取消,回复回调
    self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
    self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
    [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接收到的字节数、期望接收到的字节数、发送的字节数、期望发送的字节数进行监听
    [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];

    //对上传和下载完成的分数(NSProgress的fractionCompleted)进行监听
    [self.downloadProgress addObserver:self
                            forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                               options:NSKeyValueObservingOptionNew
                               context:NULL];
    [self.uploadProgress addObserver:self
                          forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
}
//移除task的进度
- (void)cleanUpProgressForTask:(NSURLSessionTask *)task {
    //移除task接收到的字节数、期望接收到的字节数、发送的字节数、期望发送的字节数的监听
    [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
    [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))];
    [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
    [task removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))];
    
    //移除上传,下载进度条完成的分数的监听
    [self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
    [self.uploadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))];
}

- (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];
        }
    }
    //如果是progress的监听
    else if ([object isEqual:self.downloadProgress]) {
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
    else if ([object isEqual:self.uploadProgress]) {
        if (self.uploadProgressBlock) {
            self.uploadProgressBlock(object);
        }
    }
}

关于数据的处理(当然也包括对应成功失败的回调),主要是实现那几个有获取到数据的NSURLSession的代理方法,这几个代理方法是通过AFURLSessionManager来调用的,这个后续会提到。
关于数据最终的回调都是在- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error中处理,另外比如dowmloadTask还需要处理下载中拼接数据和下载完成移动文件夹的情况。
task完成时,除了将数据、响应、错误通过completionHandler回调出去,还发出DidComplete通知,传出去userInfo(保存着请求和响应序列化对象钝、数据、文件路径、错误信息等)。有一点特别的是completionHandler会在线程组中回调,我想这边是为了让开发者可以来设定监听。

#pragma mark - NSURLSessionTaskDelegate
//请求完成的回调
//处理完成后的数据
- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    // 把响应序列化对象保存在userInfo
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //Performance Improvement from #2672
    NSData *data = nil;
    
    //把mutableData的值拿出来,并清空
    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;
    }

    // 把下载路径和下载数据保存在userInfo
    if (self.downloadFileURL) {
        userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
    } else if (data) {
        userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
    }

    // 如果完成的回调有错误信息
    if (error) {
        // 把错误信息也保存在userInfo
        userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;

        //把所有完成的回调放在completionQueue的线程组中
        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;
            }

            // 把响应对象保存在userInfo
            if (responseObject) {
                userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
            }

            // 把序列化错误信息保存在userInfo
            if (serializationError) {
                userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
            }

            //把所有完成的回调放在completionQueue的线程组中
            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);
                }

                //发出任务完成的通知(userInfo附带任务中的所有信息)
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
                });
            });
        });
    }
#pragma clang diagnostic pop
}

#pragma mark - NSURLSessionDataTaskDelegate
//开始受到数据的回调
- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    //拼接数据
    [self.mutableData appendData:data];
}

#pragma mark - NSURLSessionDownloadTaskDelegate
//完成下载的回调
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    NSError *fileManagerError = nil;
    self.downloadFileURL = nil;

    if (self.downloadTaskDidFinishDownloading) {
        //获取用户指定的文件储存的url地址
        self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (self.downloadFileURL) {
            //将储存在临时目录下的文件移动到用户指定的url地址
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];

            //如果移动失败就发出通知(移动失败了。。。)
            if (fileManagerError) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
            }
        }
    }
}

4)AFURLSessionManager(主要类,实现功能)
AFURLSessionManager包含一个NSURLSession属性,生成任务本质还是调用NSURLSession生成任务的方法,我们以创建dataTask为例,主要做了两个步骤,加锁通过session创建dataTask,为创建的dataTask设置代理人(AFURLSessionManagerTaskDelegate)。

#pragma mark - 普通请求任务
// 普通请求任务(带完成的回调)
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                            completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    return [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler];
}
// 普通请求任务(带完成的回调,以及上传进度和下载进度的回调)
- (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 {

    __block NSURLSessionDataTask *dataTask = nil;
    
    //在串行队列中同步生成task(类似加锁)
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });

    //为生成的普通请求task添加代理
    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

我们再来看看具体怎么设置代理人,这边的代理跟我们之前的代理协议啥的不一样,这边的代理人无非是AFURLSessionManager去调用代理类的方法(比如设置进度,添加通知,甚至后面的NSURLSession的代理方法)。一个任务对应一个代理类。另外,AFURLSessionManager还会将代理人保存在字典中,key是对应task的ID。
还有一点重要的是,为task添加通知(TaskDidResume和DidSuspend),这样就会剔除其他外来的任务,让该类产生的任务发出对应的通知。

//设置普通请求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;
    //为task设置代理(把dataTask传给delegate)
    [self setDelegate:delegate forTask:dataTask];

    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

//为task设置代理
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    // 断言判断参数是否为空
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    [self.lock lock];
    // 以task的id为key,AFURLSessionManagerTaskDelegate为value储存起来
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    // 代理人为task设置进度
    [delegate setupProgressForTask:task];
    //给task添加恢复和暂停的通知(AFNSURLSessionTaskDidResumeNotification和AFNSURLSessionTaskDidSuspendNotification)
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

那什么时候移除代理呢?那就是任务完成的时候。在这边我们也可以看到task完成的代理方法执行时,主动去获取对应task的代理,让他去执行对应操作。

//task完成时调用
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

    // 当在后台完成任务时,委托可能是nil
    if (delegate) {
        // 调用代理对应的方法(让代理去处理)
        [delegate URLSession:session task:task didCompleteWithError:error];
        // task完成了,移除代理
        [self removeDelegateForTask:task];
    }

    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }
}

前面我们只是讲了生成dataTask,对应的还有downloadTask,uploadTask步骤也差不多(NSURLSession生成task,添加代理)。既然生成task,也遵守了其协议,肯定实现了NSURLSession的代理方法,就如上面提到的AFURLSessionManager提供了对NSURLSession协议的自定义处理的Block(类似于代理)。
这个意思是在代理方法中你可以选择默认的处理或自定义处理(实现自定义处理的Block)。以收到dataTask收到数据的代理方法为例,默认的处理是让代理类去处理,进行数据的拼接,我们也可以实现dataTaskDidReceiveData,自己处理(这种如果用代理实现应该好理解一点)。

//dataTask收到数据时调用
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{

    //调用代理对应的方法(让代理去处理)
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    [delegate URLSession:session dataTask:dataTask didReceiveData:data];

    //自己处理
    if (self.dataTaskDidReceiveData) {
        self.dataTaskDidReceiveData(session, dataTask, data);
    }
}

关于上面提到的(2)和(3)功能都说完了,我们来说说第一个功能(1)获取正在执行的所有任务、请求任务、上传任务、下载任务。其实就是使用session的getTasksWithCompletionHandler方法。

#pragma mark -  获取当前运行的任务
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    // 创建一个信号量,值为0
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        //信号量加1
        dispatch_semaphore_signal(semaphore);
    }];

    //一直等待,知道信号量不为0才会执行到这一步
    //将计数值减1,并且执行返回
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}

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

推荐阅读更多精彩内容