美文网首页
[iOS] AFNetworking源码学习—下载

[iOS] AFNetworking源码学习—下载

作者: 木小易Ying | 来源:发表于2019-10-22 12:02 被阅读0次

    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

    相关文章

      网友评论

          本文标题:[iOS] AFNetworking源码学习—下载

          本文链接:https://www.haomeiwen.com/subject/llmomctx.html