[iOS] AFNetworking源码学习—下载

2019-10-22  本文已影响0人  木小易Ying

github:https://github.com/AFNetworking/AFNetworking

AFNetworking是OC常用的一个网络封装库,3.0以后基于NSSession实现了很多功能,例如网络监控、block监听进度之类的。

首先先来看个文件下载使用案例:

- (void)downLoad{

    //1.创建管理者对象
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    //2.确定请求的URL地址
    NSURL *url = [NSURL URLWithString:@""];
    
    //3.创建请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    //下载任务
    NSURLSessionDownloadTask *task = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
        //打印下下载进度
        WKNSLog(@"%lf",1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount);
        
    } destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
        //下载地址
        WKNSLog(@"默认下载地址:%@",targetPath);
        
        //设置下载路径,通过沙盒获取缓存地址,最后返回NSURL对象
        NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)lastObject];
        return [NSURL fileURLWithPath:filePath];
        
        
    } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
        
        //下载完成调用的方法
        WKNSLog(@"下载完成:");
        WKNSLog(@"%@--%@",response,filePath);
        
    }];
    
    //开始启动任务
    [task resume];

}

使用的时候需要先获取manager,然后创建task并开启任务。

@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>

而AFHTTPSessionManager是继承自AFURLSessionManager的,所以就从AFURLSessionManager创建开始看起吧~


1. AFURLSessionManager init

AFURLSessionManager的init方法如下:

- (instancetype)init {
    return [self initWithSessionConfiguration:nil];
}

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    self = [super init];
    if (!self) {
        return nil;
    }

    if (!configuration) {
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }

    self.sessionConfiguration = configuration;

    // 创建回调所在的NSOperationQueue
    self.operationQueue = [[NSOperationQueue alloc] init];
    self.operationQueue.maxConcurrentOperationCount = 1;

    self.responseSerializer = [AFJSONResponseSerializer serializer];

    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

#if !TARGET_OS_WATCH
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif

    // 创建一个NSMutableDictionary,存储task和task对应的delegate
    self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

    // 用于保证NSMutableDictionary多线程读写安全
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;

    __weak typeof(self) weakSelf = self;
    // 获取session已有的未完成task,然后都给他们设置上delegate,防止backgroundSession可能退出后没有结束继续执行,重新打开app init以后就找不到对应的delegate了。
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        
        __strong typeof(weakSelf) strongSelf = weakSelf;
        for (NSURLSessionDataTask *task in dataTasks) {
            [strongSelf addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
        }

        for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
            [strongSelf addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
        }

        for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
            [strongSelf addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
        }
    }];

    return self;
}

所以这段就是建configuration然后init一些实例变量,最后给session里面所有的任务重置一下delegate。

这里并没有init session啊,因为:

- (NSURLSession *)session {
    
    @synchronized (self) {
        if (!_session) {
            _session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
        }
    }
    return _session;
}

为了确保多线程安全,这里用了synchronized,并且采用懒加载的方式创建session,并且只会创建一次。

2. AFURLSessionManager实现的delegate

@interface AFURLSessionManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying>

上面我们看到了session将self(AFURLSessionManager)设为了delegate,它也的确遵从了NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate这几个协议,它在协议里面做了什么呢?

总的而言分两种,一种是在delegate里面调用相应的block,并发送通知,类似于:

- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
    if (self.sessionDidBecomeInvalid) {
        self.sessionDidBecomeInvalid(session, error);
    }

    [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}

另外一种是回调task对应的delegate并且调用self对应的block块,只有7个方法是这么做的,其他几个delegate都是第一种,这种的举例:

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

    // delegate may be nil when completing a task in the background
    if (delegate) {
        [delegate URLSession:session task:task didCompleteWithError:error];

        [self removeDelegateForTask:task];
    }

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

7个有回调task的delegate的方法分别为:

#pragma mark - NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics

#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data

#pragma mark - NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes

也就是说只有这几个回调方法每个task可以自行进行处理,其他几个回调方法都是和manager绑定的,会执行manager内部的相应block并且发通知。

3. task自身的AFURLSessionManagerTaskDelegate

在上面的代码里面看到每个task都可以设置一个AFURLSessionManagerTaskDelegate,然后再特定的几个回调里面会取出task的delegate并调用([self delegateForTask:task])。

先来看下怎么取对应的delegate:

- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);

    AFURLSessionManagerTaskDelegate *delegate = nil;
    [self.lock lock];
    delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
    [self.lock unlock];

    return delegate;
}

所以其实task和它对应的delegate是存在self.mutableTaskDelegatesKeyedByTaskIdentifier里面的,key是task的taskIdentifier,value就是delegate。

由于NSMutableDictionary不是线程安全的,所以这里需要用NSLock防止多线程同时操作。

这里其实也涉及了第一部分里面的一个设置:
为什么init里面self.operationQueue.maxConcurrentOperationCount设为1?
因为回调所在的线程是operationQueue,而回调里面有的时候是要removeTaskDelegate的,也就是会操作这个self.mutableTaskDelegatesKeyedByTaskIdentifier,那么由于它是加锁的,如果有用这个字典,就和单线程一样了,于是用maxConcurrentOperationCount限制为串行队列的感觉。

那么是在哪里把delegate加入到字典里面的呢?

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

另外在加delegate的时候还要给他们加observer:

- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
}

这个observer是干啥的呢?

- (void)af_resume {
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    NSURLSessionTaskState state = [self state];
    [self af_resume];
    
    if (state != NSURLSessionTaskStateRunning) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
    }
}

首先在task resume的时候它会抛出AFNSURLSessionTaskDidResumeNotification通知,然后manager的taskDidResume:会执行:

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

也就是说它判断了一下task的taskDescription属性是不是和自己的一致以后,就又发了一个通知AFNetworkingTaskDidResumeNotification。

那为啥要判断taskDescription呢?
因为每个task无论是不是这个afmanager创建的一旦resume都会发AFNSURLSessionTaskDidResumeNotification通知(当afmanager交换了方法以后,这个之后会说),但是manager不关心自己创建的task之外的方法的resume,所以会通过taskDescription筛选出自己的task。

这里巧用了taskDescription,毕竟如果它循环dict也可以找到task,但是如果用taskDescription给每个task加个tag就节约了保存task以及查找的时间。

manager的self.taskDescriptionForSessionTasks其实就是自己的内存地址。

- (NSString *)taskDescriptionForSessionTasks {
    return [NSString stringWithFormat:@"%p", self];
}

task的taskDescription是在add的时候加上去的:

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

当设置delegate的时候先创建一个AFURLSessionManagerTaskDelegate,然后把他的progress和complete block设置上,并且manager也设进去。这里之后可以看下delegate内部定义,manager是弱引用,所以虽然manager通过self.mutableTaskDelegatesKeyedByTaskIdentifier持有delegate,但delegate并没有持有manager。

然后调用setDelegate:,即之前看过的将delegate和相应task加入到NSMutableDictionary以及给task加observer。


这里终于引入了正题AFURLSessionManagerTaskDelegate:

@interface AFURLSessionManagerTaskDelegate : NSObject <NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
- (instancetype)initWithTask:(NSURLSessionTask *)task;
@property (nonatomic, weak) AFURLSessionManager *manager;
@property (nonatomic, strong) NSMutableData *mutableData;
@property (nonatomic, strong) NSProgress *uploadProgress;
@property (nonatomic, strong) NSProgress *downloadProgress;
@property (nonatomic, copy) NSURL *downloadFileURL;
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
@property (nonatomic, strong) NSURLSessionTaskMetrics *sessionTaskMetrics;
#endif
@property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock;
@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock;
@property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;
@end

除了进度block,它只有一些NSProgress以及弱引用了manager,还记录了url,以及用于保存接收下来的数据的mutableData,还有sessionTaskMetrics(一个用于保存response时间以及redirect count的东东)。对外只提供了initWithTask:

- (instancetype)initWithTask:(NSURLSessionTask *)task {
    self = [super init];
    if (!self) {
        return nil;
    }
    
    // 初始化_mutableData以及两个NSProgress
    _mutableData = [NSMutableData data];
    _uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
    _downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
    
    // 这里都用了weak引用,这样progress的block就不持有delegate了,防止了内存泄漏
    __weak __typeof__(task) weakTask = task;
    for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ])
    {

        // 给NSProgress设置可以取消、暂停、继续,并且当Progress被操作暂停时,task也暂停。
        progress.totalUnitCount = NSURLSessionTransferSizeUnknown;
        progress.cancellable = YES;
        progress.cancellationHandler = ^{
            [weakTask cancel];
        };
        progress.pausable = YES;
        progress.pausingHandler = ^{
            [weakTask suspend];
        };
#if AF_CAN_USE_AT_AVAILABLE
        if (@available(iOS 9, macOS 10.11, *))
#else
        if ([progress respondsToSelector:@selector(setResumingHandler:)])
#endif
        {
            progress.resumingHandler = ^{
                [weakTask resume];
            };
        }
        
        // 最后给progress加observer,监听NSProgress的fractionCompleted属性,也就是完成的百分比
        [progress addObserver:self
                   forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                      options:NSKeyValueObservingOptionNew
                      context:NULL];
    }
    return self;
}

初始化delegate的时候监听了两个progress变量的fractionCompleted属性,当它们变化以后会触发:

#pragma mark - NSProgress Tracking

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
   if ([object isEqual:self.downloadProgress]) {
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
    else if ([object isEqual:self.uploadProgress]) {
        if (self.uploadProgressBlock) {
            self.uploadProgressBlock(object);
        }
    }
}

也就是说如果我们修改NSProgress的进度,delegate的block就会回调,并且把新的fractionCompleted进度百分比传给block。

然后AFURLSessionManagerTaskDelegate还实现了NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate。

AFURLSessionManagerTaskDelegate

还记得manager也遵循了这些delegate,并且把7个方法转调了task的delegate的方法么,其实就是这7个。

NSURLSessionDataDelegate - didReceiveData
#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];
}

这个回调是接收数据过程中不断回调的,会把新接收到的data传入,delegate将每次接收到的append给self.mutableData,并且更改了self.downloadProgress.completedUnitCount以及self.downloadProgress.totalUnitCount,于是downloadProgress的fractionComplete就自动更改了,会触发KVO调用self.downloadProgressBlock。

NSURLSessionDataDelegate - didSendBodyData
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
    
    self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
    self.uploadProgress.completedUnitCount = task.countOfBytesSent;
}

这个是上传过程中会不断回调的一个函数,和didReceiveData类似,只是因为上传不需要记录data,所以只要改一下updateProgress触发self.uploadProgressBlock就可以啦。

NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
    
    self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
    self.downloadProgress.completedUnitCount = totalBytesWritten;
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{
    
    self.downloadProgress.totalUnitCount = expectedTotalBytes;
    self.downloadProgress.completedUnitCount = fileOffset;
}

和NSURLSessionDataDelegate的两个delegate相似,也是设置progress触发KVO的。

但是这两个方法并不提供data哦,所以不用append data给self.mutableData。(download task可以用resume data)

在finish的时候会调用:

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    // finish以后会把url置空
    self.downloadFileURL = nil;

    // 如果有设置downloadTaskDidFinishDownloading block,则会执行拿到要把数据move到哪个目录
    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];
            }
        }
    }
}

此时会把self.downloadFileURL置空,并且通过self.downloadTaskDidFinishDownloading得到要把下载好的tmp数据放到哪里存起来,存储位置放入self.downloadFileURL

@property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;

typedef NSURL * (^AFURLSessionDownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location);

可以看到这个block会接收task以及location,然后返回一个NSURL,也就是文件位置。

NSURLSessionTaskDelegate - didFinishCollectingMetrics
#if AF_CAN_INCLUDE_SESSION_TASK_METRICS
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
    self.sessionTaskMetrics = metrics;
}
#endif

这里只是将NSURLSessionTaskMetrics存起来给实例变量,类里面没怎么用,如果外面用可以读,内容大概就是:

(Task Interval) <_NSConcreteDateInterval: 0x600000f50080> (Start Date) 2019-10-22 02:02:08 +0000 + (Duration) 0.193185 seconds = (End Date) 2019-10-22 02:02:08 +0000
(Redirect Count) 0
(Transaction Metrics) (Request) <NSURLRequest: 0x600000d34a50> { URL: https://api.app.net/stream/0/posts/stream/global }
(Response) (null)
(Fetch Start) 2019-10-22 02:02:08 +0000
(Domain Lookup Start) (null)
(Domain Lookup End) (null)
(Connect Start) (null)
(Secure Connection Start) (null)
(Secure Connection End) (null)
(Connect End) (null)
(Request Start) 2019-10-22 02:02:08 +0000
(Request End) 2019-10-22 02:02:08 +0000
(Response Start) 2019-10-22 02:02:08 +0000
(Response End) (null)
(Protocol Name) (null)
(Proxy Connection) NO
(Reused Connection) YES
(Fetch Type) Unknown

其实这个就是保存一下类似命令行ping一个IP以后的各种时间以及redirect。

NSURLSessionTaskDelegate - didCompleteWithError
- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    // manager是weak属性的,这里为了在范围内不让系统回收manager,会先转成strong持有
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    // 获取manager的responseSerializer用于parse response
    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //用局部变量保存self.mutableData,然后去除self.mutableData的持有
    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 AF_CAN_USE_AT_AVAILABLE && AF_CAN_INCLUDE_SESSION_TASK_METRICS
    if (@available(iOS 10, macOS 10.12, watchOS 3, tvOS 10, *)) {
        if (self.sessionTaskMetrics) {
            userInfo[AFNetworkingTaskDidCompleteSessionTaskMetrics] = self.sessionTaskMetrics;
        }
    }
#endif

    // 如果self.downloadFileURL不为空,说明已经走过NSURLSessionDownloadDelegate的didFinishDownloadingToURL了,已经转存了下载好的文件了
    if (self.downloadFileURL) {
        userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
    } else if (data) {

        // 如果没有转存好file,就把接收到的data都放入userinfo给外面
        userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
    }

    if (error) {
        // 如果有error就异步抛到group,发一个异步的AFNetworkingTaskDidCompleteNotification通知并且把空的responseObject和error传回completionHandler
        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 {
        // 如果没有error
        dispatch_async(url_session_manager_processing_queue(), ^{
            NSError *serializationError = nil;
            // 用manager.responseSerializer给response parse一下然后得到responseObject
            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;
            }

            // 最后同样是将response和error传回completionHandler,然后发送AFNetworkingTaskDidCompleteNotification通知
            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];
                });
            });
        });
    }
}

所以这个delegate内部已经用manager的responseSerializer将response解析好了,放入了responseObject传回completionHandler了,但如果有downloadFileURL那么responseObject就会被设置为存储位置downloadFileURL。

@property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;

typedef void (^AFURLSessionTaskCompletionHandler)(NSURLResponse *response, id responseObject, NSError *error);

外部还可以通过监听AFNetworkingTaskDidCompleteNotification拿到userinfo,然后通过各种key拿到sessionTaskMetrics、responseSerializer、assetPath、data之类的。

4. manager如何创建task

先来看下download task怎么创建滴:

- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
                                             progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                          destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                    completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    __block NSURLSessionDownloadTask *downloadTask = nil;
    url_session_manager_create_task_safely(^{
        downloadTask = [self.session downloadTaskWithRequest:request];
    });

    [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];

    return downloadTask;
}

这里看到其实就是通过session来创建了一个task,只是放入了url_session_manager_create_task_safely里面。最后再add一下task的delegate。

这里用sync同步的方式创建task,也是因为如果不用同步,下一步到抛到addDelegate的时候需要给delegate以及task建立映射,如果task还是nil,建立映射也没有意义,所以必须等待task创建完毕才能执行下一步。

但是url_session_manager_create_task_safely是啥嘞:

static void url_session_manager_create_task_safely(dispatch_block_t _Nonnull block) {
    if (block != NULL) {
        if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
            // Fix of bug
            // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
            // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
            dispatch_sync(url_session_manager_creation_queue(), block);
        } else {
            block();
        }
    }
}

#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0
#endif

也就是说在ios8.0以下的时候,会通过同步抛入url_session_manager_creation_queue创建一个task,如果ios8.0以上就直接创建。

原因其实是:iOS 8修复了一个bug,多线程下创建的task小概率拥有同样的task identifier。但是AFNetworking使用 task identifier作为manager的mutableTaskDelegatesKeyedByTaskIdentifier的key,需要task identifier唯一。

url_session_manager_creation_queue其实就是创建了一个串行队列:

static dispatch_queue_t url_session_manager_creation_queue() {
    static dispatch_queue_t af_url_session_manager_creation_queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
    });

    return af_url_session_manager_creation_queue;
}

除此之外,

manager内部通过dispatch_once创建了static的:

  • url_session_manager_creation_queue 创建串行queue
  • url_session_manager_processing_queue 创建并行queue
  • url_session_manager_completion_group 创建任务完成后completionHandler抛入的group

现在来看下创建完task以后的addDelegate做了什么:

- (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
                          progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                       destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                 completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:downloadTask];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    if (destination) {
        delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
            return destination(location, task.response);
        };
    }

    downloadTask.taskDescription = self.taskDescriptionForSessionTasks;

    [self setDelegate:delegate forTask:downloadTask];

    delegate.downloadProgressBlock = downloadProgressBlock;
}

我们之前也看了addDelegateForDataTask,其实addDelegateForDownloadTask和他差不多,都是先init一个AFURLSessionManagerTaskDelegate,然后将delegate的completionHandler、uploadProgressBlock 、downloadProgressBlock、taskDescription之类的设好。最后通过setDelegate建立映射以及observer。

addDelegateForDownloadTask比其他两个多了一个destination,也就是下载后的文件要放在哪里。通过设置delegate.downloadTaskDidFinishDownloading即可,接收location和task.response参数,返回一个NSURL,即文件保存地址。AFURLSessionManagerTaskDelegate会自动在下载结束后将文件移动到这个URL的。

所以其实task启动以后,进度之类的先是回掉了session的delegate-AFURLSessionManager,然后AFURLSessionManager会把一些特定的方法转调task的delegate-AFURLSessionManagerTaskDelegate,然后通过设置AFURLSessionManagerTaskDelegate的成员变量,引发KVO监听,自动回调AFURLSessionManagerTaskDelegate的progressBlock以及completionBlock。

5. resume data

download task是可以通过resume data创建的也是:

- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
                                                progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                             destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                       completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    __block NSURLSessionDownloadTask *downloadTask = nil;
    url_session_manager_create_task_safely(^{
        downloadTask = [self.session downloadTaskWithResumeData:resumeData];
    });

    [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];

    return downloadTask;
}

那么如何获取resumeData呢?
在AFURLSessionManagerTaskDelegate 的didCompleteWithError里面将error回传给了completionHandler,所以可以在completionHandler通过error的userinfo来拿到resume data。

if (error) {
    if ([error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]) {
        NSData *data = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
        self.resumeData = data;
        // todo 保存resume data到沙盒
    }
}

到此几乎下载是如何实现的就写完啦,之后会搞一下其他的几个模块以及一些好的地方吧~

参考:
https://www.jianshu.com/p/ddf79f0763a7
https://www.jianshu.com/p/856f0e26279d
https://www.jianshu.com/p/170243957f75
使用:https://www.jianshu.com/p/40b076870460
http://ju.outofmemory.cn/entry/295593

上一篇下一篇

猜你喜欢

热点阅读