关于一次完整http_data_Task(基于AFN)的过程步骤源码详解:

1,872 阅读8分钟

前言:

最近想自己基于NSURLSession封一个网络框架,但是试了几次之后发现总是考虑的不全面,于是决定好好读下AFN的源码再进行设计,不得不说,AFN考虑的东西真的很全面。站在巨人的肩膀上?虽然可能自己写的框架不如AFN好用,但是可以增强自己的理解于认识,更好的理解运行机制。

于是,决定以一次最简单的HTTP_data_ Task 请求为例,详细说下AFN的请求步骤与机制: (代码注释已添加)


AFURLSessionManager(最重要的类)

AFHTTPSessionManager是使用AFURLSessionManager进行HTTP请求的便捷方法的子类,其实根本的task请求还是从AFURLSessionManager中的方法发出的,因此就跳过AFHTTPSessionManager的Request封装过程,直接从AFURLSessionManager的方法始:

先说下主要的属性,后面的方法中会用到

.h:

常用属性:

1.维护一个NSURLSession:

@property (readonly, nonatomic, strong) NSURLSession *session;

2.NSURLSession的OperationQueue:

就是NSURLSession的回调所在的队列,默认是子线程的串行队列,也无法改变,NSURLSession本来回调就是在子线程中进行的。 @property (readonly, nonatomic, strong) NSOperationQueue *operationQueue; 这会在AFURLSessionManager的初始化中进行设定。

//会在AFN内部初始化一个操作队列(根据后面的maxcount,该队列为串行队列)
    self.operationQueue = [[NSOperationQueue alloc] init];
    //当前请求最大并发数为1(苹果规定,iOS端一个IP的最大访问进程数目是4)
    //最大并发数为1,也就证明了AFN内部的task是串行执行的
    self.operationQueue.maxConcurrentOperationCount = 1;
    //维护AFNURLSessionManager内部的NSURLSession
    //设置回调的队列
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
3.completionQueue:处理请求回调的调度队列

如果NULL(默认),则使用主队列。 这也就是AFN的block的回调默认都是在主线程中进行的原因。 此时Session的内部的回调在内部的匿名子线程进行,然后AFN会在子线程中吊起主线程,传入successBlock或者failureBlock,在主线程中进行解析。

/**
 The dispatch queue for `completionBlock`. If `NULL` (default), the main queue is used.
 */
@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;
4.调度组completionGroup

如果是用户不指定group,NULL(默认),则使用AFN内部的私人派遣组。 @property (nonatomic, strong, nullable) dispatch_group_t completionGroup;

5.session当前的task:
//全部请求task
@property(readonly,nonatomic,strong)NSArray *tasks;
//data请求task
@property(readonly,nonatomic,strong)NSArray *dataTasks;
//上传task
@property(readonly,nonatomic,strong)NSArray *uploadTasks;
//下载task
@property(readonly,nonatomic,strong)NSArray *downloadTasks;
6.是否重新创建任务attemptsToRecreateUploadTasksForBackgroundSessions
@property (nonatomic, assign) BOOL attemptsToRecreateUploadTasksForBackgroundSessions

这个属性非常重要,在iOS7中存在一个bug,在创建后台上传任务时,有时候会返回nil,所以为了解决这个问题,AFNetworking遵照了苹果的建议,在创建失败的时候,会重新尝试创建,次数默认为3次,所以你的应用如果有场景会有在后台上传的情况的话,记得将该值设为YES,避免出现上传失败的问题,默认是NO。

.m

方法的具体请求步骤:

最常用的方法入口:AFHTTPSessionManager的GET,POST请求什么的,最后基本都是走AFNURLSessionManager的这个入口:

//request已经在AFNHTTPSessionManager中封装好了
- (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

具体实现:

- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                     progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                      success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                      failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
//收到最终处理完的task
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];
    //提交请求
    [dataTask resume];
    return dataTask;
}

进入dataTaskWithHTTPMethod方法,接收task,并为task加上处理并返回task

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    if (serializationError) {
        if (failure) {
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
        }

        return nil;
    }

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

再进入dataTaskWithRequest:uploadProgress:downloadProgress: completionHandler:方法生成task并返回:

.h中的属性:dataTaskuploadTaskdownloadTask实际上都是completionHanlder block返回出来的,但是我们知道网络请求是delegate返回结果的,AF内部做了巧妙的操作,他对每个task都增加代理设置

- (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;
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

进入- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask:uploadProgress:downloadProgress:completionHandler:为task添加delegate,并将block赋值给delegate的block属性

- (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是weak,跟session的delegate不同。session的delegate很特殊
    delegate.manager = self;
    //将处理的block赋值给delegate,目的是在Session的delegate回调中进行主线程回调block,如果AFN指定了操作队列,则在指定的操作队列中进行回调
    delegate.completionHandler = completionHandler;
    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
// 设置task的delegate
    [self setDelegate:delegate forTask:dataTask];
// 设置上传和下载进度回调
    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

然后delegate对象利用KVO将task对一些方法进行监听,注册的通知是tasksuspendresume,监听到变化时,delegate扔出block

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    // 断言
    NSParameterAssert(task);
    NSParameterAssert(delegate);
    [self.lock lock];
 self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    // task使用kvo对一些方法监听,返回上传或者下载的进度
    [delegate setupProgressForTask:task];
    // sessionManager对暂停task和恢复task进行注册通知
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

关于setupProgressForTask再往下,主要是对task和progress设置监听,以及一些异常处理操作,这里不再进行继续深入了。

=============================================== #####至此,一次AFNURLSessionManager的Task请求完成 请求的回调还是NSURLSession的那些代理 【NSURLSessionDataDelegate,NSURLSessionDataDelegate,NSURLSessionDownloadDelegate'】,只是AFN把他们封装了,加上了必要的处理,使得我们直接在AFNHTTPSessionMAnager的completionHandler block就可以完成回调的处理了.

####核心的回调方法有三个,依次是:

  • 接受到数据的回调:didReceiveData
  • task完成的回调
  • 下载完成的回调(只有downloadTask才会触发)

##下面以普通的data_Task为例子:

###首先是didReceiveData 没什么要说的,就是拼接data以供下个方法操作

#pragma mark - NSURLSessionDataDelegate

- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
    self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;

    [self.mutableData appendData:data];
}

###再是NSURLSessionTaskDelegate的 URLSession:task:didReceiveChallenge:completionHandler:

说明:首先会把返回的数据赋给一个局部data,然后将全局的mutableData置空,这样就可以保证下次请求的数据是重新加载。这里区分了下载和普通的数据返回,如果是下载的话,直接下载到指定文件路径中,如果用户指定这个路径的话,userInfo字典里面存的就是下载路径,否则,存的是下载数据。 然后就是判断有没有错误,错误的话把错误返回,返回值task.response, responseObject此处是空的,然后就是error。最后在主线程中发送通知,为当前task的userInfo,注意此处的userInfo只是用来做通知信息的。而我们平时用的时候不会用通知来获取请求成功的回调,这个通知是为了AFNetworking中的UIKit封装部分服务的。(反正我不喜欢用AFN的UIKit) 然后就是成功的回调,异步请求在singleton队列中,这里对队列的生成都加了singleton的保护。这里通过responseSerializer 对结果数据进行转化成对应的格式(参考ADN的Serialization部分官方文档,[http://cocoadocs.org/docsets/AFNetworking/3.1.0/Classes/AFURLSessionManager.html#//api/name/dataTaskWithRequest:completionHandler: ]里面讲解如何转化的),如果是下载的话,responseObject直接赋值成downloadFileURL,也就是下载的话,回调中只会有下载的目标地址。然后就是对userInfo的AFNetworkingTaskDidCompleteSerializedResponseKey(序列化响应结果)、AFNetworkingTaskDidCompleteErrorKey(序列化过程中的错误信息)进行赋值,不得不说AFNetworking对各个部分的情况都返回回去了,做的很详细。 然后就是调用回调block:completionHandler: 返回值task.response也就是完整的返回头信息以及返回的状态码。 responseObject是返回的数据或者是下载的目标地址。 serializationError注意这个地方的错误是序列化的错误,也就是此处如果对返回数据序列化产生错误,也会照样返回成功回调,只是回调结果会是序列化的错误。 最后还是一样的发送通知给AFN的封的UIKit

task回调响应(代码已经添加注释):

#pragma mark - NSURLSessionTaskDelegate

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    //因为delegate的manager是weak指针,所以使用weak-strong-dance:使得回调期间manager不被释放
    __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;
            }
            /*
             1,如果用户指定了group则向该group中异步提交回调(*请求发出是顺序同步执行,但是回调的执行是异步操作,因为这样更快啊)
             2,如果用户未指定group则使用Manager本身的默认私有group进行回调任务处理
             3,无论是默认的私有group还是用户自己的group,任务回调都是在主线程中执行的
             */
            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(), ^{
                    // 调主线程发出通知,跨层数据流通会考虑使用。。。反正我没用过这个通知
                    // 此处任务完成的通知只是为了UIKit中的一些类别中拿到回调。userInfo字段并没有返回给外部,而是给AFN的UIKit用的。当然我们可以用这个通知来获取到。
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
                });
            });
        });
    }
}

至此一次请求的发生与回调完成

end.THX for reading.