AFNetworking源码分析 (2)--NSURLSessionDownloadTask

系列文章:

AFNetWorking源码分析 (1)--AFHTTPSessionManager

在上一章节中我们通过一个具体的实例讲解了AF是如何处理一下网络请求的,本章将通过下载的实例(上传的实现类似不再做额外分析)作为入口,再做进一步分析。

1.NSURLSessionDownloadTask

AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://dn-arnold.qbox.me/Snip.zip"]
                                                            cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
                                                        timeoutInterval:10];
self.downloadTask = [session downloadTaskWithRequest:request
                                                                progress:^(NSProgress * _Nonnull downloadProgress) {
                                                                    CGFloat progress = 1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount;
                                                                    NSLog(@"下载进度 :%.2f",progress);
                                                                }
                                                             destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
                                                                 // 下载文件存储的路径
                                                                 NSString * path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
                                                                 path = [path stringByAppendingPathComponent:response.suggestedFilename];
                                                                 NSLog(@"下载路径 :%@",path);
                                                                 return [NSURL fileURLWithPath:path];
                                                             } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
                                                                 if(!error){
                                                                     NSLog(@"下载完成: %@",[filePath path]);
                                                                 }
                                                             }];
[_downloadTask resume];

1.1 初始化task


 // 1.根据请求获取下载的task
 url_session_manager_create_task_safely(^{
        downloadTask = [self.session downloadTaskWithRequest:request];
    });

AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
delegate.manager = self;
delegate.completionHandler = completionHandler;

// 2.将传入的下载路径的block存储到代理实例中
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];

// 3.将下载进度的block也存储到代理对象中
delegate.downloadProgressBlock = downloadProgressBlock;

1.2 下载完成

AFURLSessionManager.m

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    if (self.downloadTaskDidFinishDownloading) {
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (fileURL) {
            delegate.downloadFileURL = fileURL;
            NSError *error = nil;
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
            if (error) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
            }

            return;
        }
    }

    // task绑定的deledate也执行共有的数据处理方法
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
    }
}

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    NSError *fileManagerError = nil;
    self.downloadFileURL = nil;

    if (self.downloadTaskDidFinishDownloading) {
        self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (self.downloadFileURL) {
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];

            if (fileManagerError) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
            }
        }
    }
}

上述的两个实现,达到的目的是一样的:获取下载存储的路径,将下载的文件move到传入的下载的路径。但是为什么需要实现两遍?原来是AFURLSessionManager提供的对外的接口,可以将下载路径的回调传入到session中。

接口:

/**
 Sets a block to be executed when a download task has completed a download, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didFinishDownloadingToURL:`.

 @param block A block object to be executed when a download task has completed. The block returns the URL the download should be moved to, and takes three arguments: the session, the download task, and the temporary location of the downloaded file. If the file manager encounters an error while attempting to move the temporary file to the destination, an `AFURLSessionDownloadTaskDidFailToMoveFileNotification` will be posted, with the download task as its object, and the user info of the error.
 */
- (void)setDownloadTaskDidFinishDownloadingBlock:(nullable NSURL * _Nullable  (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block;

使用:

[session setDownloadTaskDidFinishDownloadingBlock:^NSURL * _Nullable(NSURLSession * _Nonnull session, NSURLSessionDownloadTask * _Nonnull downloadTask, NSURL * _Nonnull location) {
    NSString * path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    path = [path stringByAppendingPathComponent:@"result.zip"];
    return [NSURL fileURLWithPath:path];
}];

1.3 下载进度

session可以调用如下的接口传入下载进度与重启下载的回调,以便实现自己的可定制的逻辑。

/**
 Sets a block to be executed periodically to track download progress, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:`.

 @param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes five arguments: the session, the download task, the number of bytes read since the last time the download progress block was called, the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times, and will execute on the session manager operation queue.
 */
- (void)setDownloadTaskDidWriteDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block;

/**
 Sets a block to be executed when a download task has been resumed, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`.

 @param block A block object to be executed when a download task has been resumed. The block has no return value and takes four arguments: the session, the download task, the file offset of the resumed download, and the total number of bytes expected to be downloaded.
 */
- (void)setDownloadTaskDidResumeBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes))block;

使用:

[_session setDownloadTaskDidWriteDataBlock:^(NSURLSession * _Nonnull session, NSURLSessionDownloadTask * _Nonnull downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite) {
    NSLog(@"下载进度 %lld/%lld",totalBytesWritten,totalBytesExpectedToWrite);
}];
    
[_session setDownloadTaskDidResumeBlock:^(NSURLSession * _Nonnull session, NSURLSessionDownloadTask * _Nonnull downloadTask, int64_t fileOffset, int64_t expectedTotalBytes) {
    NSLog(@"重启下载 %lld %lld",fileOffset,expectedTotalBytes);
}];
而AF是是如何处理下载进度监听的呢?

(1)绑定下载进度的回调
上述1.1 初始化task中:[self setDelegate:delegate forTask:downloadTask];实现了对下载进度的监听,我们看看有关下载部分的具体代码:

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    ...
    [delegate setupProgressForTask:task];
    ...
}

- (void)setupProgressForTask:(NSURLSessionTask *)task {
    __weak __typeof__(task) weakTask = task;

    self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
    [self.downloadProgress setCancellable:YES];
    // 1.下载取消的回调
    [self.downloadProgress setCancellationHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask cancel];
    }];
    
    // 2.下载暂停的回调
    [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];
        }];
    }
 
    // 1.kvo的方式监听task countOfBytesReceived,countOfBytesExpectedToReceive属性的变化。
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
              options:NSKeyValueObservingOptionNew
              context:NULL];
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
              options:NSKeyValueObservingOptionNew
              context:NULL];

   // 2.kvo的方式监听fractionCompleted即下载进度的变化。
    [self.downloadProgress addObserver:self
                            forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                               options:NSKeyValueObservingOptionNew
                               context:NULL];
}

(2)具体进度变化的触发

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
        
         // 1.收到task countOfBytesReceived属性变化时更新downloadProgress的下载完成的总数
            self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
        
         // 2.收到task countOfBytesExpectedToReceive属性变化时更新downloadProgress的下载总数
            self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        }
    }
    else if ([object isEqual:self.downloadProgress]) {
    
        // 3.收到downloadProgressBlock fractionCompleted属性变化时触发上层传入的下载进度的回调
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
}

1.4 下载暂停,取消

在1.3 downloadProgress中我们看到了下载暂停与取消的回调的设置,我们看看如何触发回调,先看外部获取task的downloadProgress实例的接口:

/**
 Returns the download progress of the specified task.

 @param task The session task. Must not be `nil`.

 @return An `NSProgress` object reporting the download progress of a task, or `nil` if the progress is unavailable.
 */
- (nullable NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task;

拿到了task对应的downloadProgress的实例,就可以执行cancel,pause的方法,触发downloadProgress设置的回调,进而改变task的状态,具体调用实例:

[[session downloadProgressForTask:downloadTask] pause];

[[session downloadProgressForTask:downloadTask] cancel];

当然也可以直接操作task:

[_downloadTask suspend];
[_downloadTask resume];

2.关于断点续传

网络下载中难免不需要考虑断点续传的问题。

2.1 非退出程序的断点续传

这种需求,原生的Api支持,且同时也支持后台下载,具体看demo代码:

typedef void   (^DownloadProgressBlock)(NSProgress *downloadProgress);
typedef NSURL* (^DownloadDestinationBlock)(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response);
typedef void   (^DownloadCompletionBlock)(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error);

- (void)initUI{
    UIButton *startButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 40)];
    startButton.center = CGPointMake(self.view.center.x, 100);
    startButton.backgroundColor = [UIColor redColor];
    [startButton setTitle:@"开始" forState:UIControlStateNormal];
    [startButton addTarget:self action:@selector(start:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:startButton];
    
    UIButton *pauseButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 40)];
    pauseButton.center = CGPointMake(self.view.center.x, 200);
    pauseButton.backgroundColor = [UIColor redColor];
    [pauseButton setTitle:@"停止" forState:UIControlStateNormal];
    [pauseButton addTarget:self action:@selector(cancel:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:pauseButton];
}

- (void)cancel:(id)sender{
     NSLog(@"下载取消");
    __weak typeof(self) weakSelf = self;
    [_downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        weakSelf.resumeData = resumeData;
    }];
}

- (void)start:(id)sender{
    NSLog(@"下载开始");
    DownloadProgressBlock  downloadProgressBlock = ^(NSProgress * _Nonnull downloadProgress) {
        CGFloat progress = 1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount;
        NSLog(@"下载进度 :%.2f",progress);
    };
    
    DownloadDestinationBlock  downloadDestinationBlock = ^NSURL*(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
        // 下载文件存储的路径
        NSString * path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
        path = [path stringByAppendingPathComponent:response.suggestedFilename];
        NSLog(@"下载路径 :%@",path);
       return [NSURL fileURLWithPath:path];
    };
    
    DownloadCompletionBlock downloadCompletionBlock = ^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
        if(!error){
            NSLog(@"下载完成: %@",[filePath path]);
        }
    };
    
    if(_resumeData && _resumeData.length > 0){
        self.downloadTask = [_session downloadTaskWithResumeData:_resumeData
                                                        progress:downloadProgressBlock
                                                     destination:downloadDestinationBlock
                                               completionHandler:downloadCompletionBlock];
    }else{
        self.session = [AFHTTPSessionManager manager];
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"https://dn-arnold.qbox.me/Dash.zip"]];
        self.downloadTask = [_session downloadTaskWithRequest:request
                                                     progress:downloadProgressBlock
                                                  destination:downloadDestinationBlock
                                            completionHandler:downloadCompletionBlock];
    }
    [_downloadTask resume];
}
运行结果:
2017-06-03 17:38:02.678 InterView[46838:517522] 下载开始
2017-06-03 17:38:03.007 InterView[46838:517748] 下载进度 :0.01
2017-06-03 17:38:03.070 InterView[46838:517738] 下载进度 :0.01
2017-06-03 17:38:03.091 InterView[46838:517743] 下载进度 :0.02
2017-06-03 17:38:03.139 InterView[46838:517738] 下载进度 :0.02
2017-06-03 17:38:03.212 InterView[46838:517743] 下载进度 :0.03
2017-06-03 17:38:03.277 InterView[46838:517743] 下载进度 :0.03
2017-06-03 17:38:03.358 InterView[46838:517738] 下载进度 :0.04
2017-06-03 17:38:03.461 InterView[46838:517738] 下载进度 :0.04
2017-06-03 17:38:03.532 InterView[46838:517748] 下载进度 :0.05
2017-06-03 17:38:03.552 InterView[46838:517522] 下载取消
2017-06-03 17:38:05.285 InterView[46838:517522] 下载开始
2017-06-03 17:38:05.637 InterView[46838:517738] 下载进度 :0.05
2017-06-03 17:38:05.637 InterView[46838:517738] 下载进度 :0.05
2017-06-03 17:38:05.638 InterView[46838:517738] 下载进度 :0.05
2017-06-03 17:38:05.718 InterView[46838:517743] 下载进度 :0.05
2017-06-03 17:38:05.742 InterView[46838:517801] 下载进度 :0.06
2017-06-03 17:38:05.759 InterView[46838:517768] 下载进度 :0.06
2017-06-03 17:38:05.804 InterView[46838:517748] 下载进度 :0.06
2017-06-03 17:38:05.899 InterView[46838:517738] 下载进度 :0.07
2017-06-03 17:38:05.964 InterView[46838:517738] 下载进度 :0.07
2017-06-03 17:38:06.035 InterView[46838:517738] 下载进度 :0.08
2017-06-03 17:38:06.106 InterView[46838:517743] 下载进度 :0.08
2017-06-03 17:38:06.172 InterView[46838:517801] 下载进度 :0.09
2017-06-03 17:38:06.236 InterView[46838:517768] 下载进度 :0.09
2017-06-03 17:38:06.300 InterView[46838:517522] 下载取消

2.2 退出程序的断点续传

具体的业务中存在用户退出程序,重启进入App依旧下载任务依旧能断点续传,如果需要实现这种场景就必须存在临时的文件存储,以便重启读取数据,查看了相关的Api目前尚未找到实现方式。查看了网上的资料参考了:使用NSURLSession程序退出后继续下载中的的思想实现了这一需求,写了一份Demo代码测试一下:

- (void)cancel:(id)sender{
     NSLog(@"下载取消");
    [_downloadTask cancel];
}

- (void)start:(id)sender{
    NSLog(@"下载开始");
    DownloadProgressBlock  downloadProgressBlock = ^(NSProgress * _Nonnull downloadProgress) {
        dispatch_async(dispatch_get_main_queue(), ^{
            CGFloat progress = 1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount;
            NSString *progressString = [NSString stringWithFormat:@"下载进度 :%.2f",progress];
            UILabel *progressLabel = [self.view viewWithTag:1025];
            progressLabel.text = progressString;
        });
    };
    
    DownloadDestinationBlock  downloadDestinationBlock = ^NSURL*(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
        // 下载文件存储的路径
        NSString * path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
        path = [path stringByAppendingPathComponent:response.suggestedFilename];
        NSLog(@"下载路径 :%@",path);
       return [NSURL fileURLWithPath:path];
    };
    
    DownloadCompletionBlock downloadCompletionBlock = ^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
        if(!error){
            [[NSUserDefaults standardUserDefaults] removeObjectForKey:_currentDownloadUrl];
            [[NSUserDefaults standardUserDefaults] synchronize];
            NSLog(@"下载完成: %@",[filePath path]);
        }
    };
    self.currentDownloadUrl = @"https://dn-arnold.qbox.me/Dash.zip";
    self.session = [AFHTTPSessionManager manager];
    NSData *resumeData;
    NSString *resumeDataFilePath = [[NSUserDefaults standardUserDefaults] objectForKey:_currentDownloadUrl];
    if(resumeDataFilePath && resumeDataFilePath.length > 0){
        if([[NSFileManager defaultManager] fileExistsAtPath:resumeDataFilePath]){
            resumeData = [self resumeDataFromFilePath:resumeDataFilePath];
        }
    }
    if(resumeData && resumeData.length > 0){
        self.downloadTask = [_session downloadTaskWithResumeData:resumeData
                                                        progress:downloadProgressBlock
                                                     destination:downloadDestinationBlock
                                               completionHandler:downloadCompletionBlock];
    }else{
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:_currentDownloadUrl]];
        self.downloadTask = [_session downloadTaskWithRequest:request
                                                     progress:downloadProgressBlock
                                                  destination:downloadDestinationBlock
                                            completionHandler:downloadCompletionBlock];
        resumeDataFilePath = [self resumeDataFilePathFor:_downloadTask
                                             downloadUrl:_currentDownloadUrl];
        [[NSUserDefaults standardUserDefaults] setObject:resumeDataFilePath
                                                  forKey:_currentDownloadUrl];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
    [_downloadTask resume];
}

// 拼接断点续传初始化传入的resumeData数据
- (NSData *)resumeDataFromFilePath:(NSString *)filePath{

    NSData *resumeData=[[NSData alloc] initWithContentsOfFile:filePath];
    if(resumeData && resumeData.length>0){
        NSMutableDictionary *resumeDataDict = [NSMutableDictionary dictionary];
        NSMutableURLRequest *newResumeRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:_currentDownloadUrl]];
        [newResumeRequest addValue:[NSString stringWithFormat:@"bytes=%ld-",resumeData.length] forHTTPHeaderField:@"Range"];
        NSData *newResumeRequestData = [NSKeyedArchiver archivedDataWithRootObject:newResumeRequest];
        [resumeDataDict setObject:_currentDownloadUrl
                           forKey:@"NSURLSessionDownloadURL"]; // 需要这一句不然会报错Code=-1002 "unsupported URL"
        [resumeDataDict setObject:[NSNumber numberWithInteger:resumeData.length]
                           forKey:@"NSURLSessionResumeBytesReceived"];
        
        [resumeDataDict setObject:newResumeRequestData
                           forKey:@"NSURLSessionResumeCurrentRequest"];
        [resumeDataDict setObject:[filePath lastPathComponent]
                           forKey:@"NSURLSessionResumeInfoTempFileName"];
        NSData *resumeData = [NSPropertyListSerialization
                              dataWithPropertyList:resumeDataDict
                              format:NSPropertyListBinaryFormat_v1_0
                              options:0
                              error:nil];
        return resumeData;
    }
    
    return nil;
}

// 利用runtime的方式根据task获取相应的临时下载的目录
- (NSString *)resumeDataFilePathFor:(NSURLSessionDownloadTask *)downloadTask
                        downloadUrl:(NSString *)downloadUrl{
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList([downloadTask class], &outCount);
    for (i = 0; i<outCount; i++) {
        objc_property_t property = properties[i];
        const char* char_f =property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:char_f];
        if ([@"downloadFile" isEqualToString:propertyName]) {
            id propertyValue = [downloadTask valueForKey:(NSString *)propertyName];
            unsigned int downloadFileoutCount, downloadFileIndex;
            objc_property_t *downloadFileproperties = class_copyPropertyList([propertyValue class], &downloadFileoutCount);
            for (downloadFileIndex = 0; downloadFileIndex < downloadFileoutCount; downloadFileIndex++) {
                objc_property_t downloadFileproperty = downloadFileproperties[downloadFileIndex];
                const char* downloadFilechar_f =property_getName(downloadFileproperty);
                NSString *downloadFilepropertyName = [NSString stringWithUTF8String:downloadFilechar_f];
                if([@"path" isEqualToString:downloadFilepropertyName]){
                    id downloadFilepropertyValue = [propertyValue valueForKey:(NSString *)downloadFilepropertyName];
                    if(downloadFilepropertyValue){
                        return downloadFilepropertyValue;
                    }
                    break;
                }
            }
            free(downloadFileproperties);
        }else {
            continue;
        }
    }
    free(properties);
    return nil;
}

上述代码实现比较粗糙,只是实现核心内容,demo测试基本达到了效果。

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

推荐阅读更多精彩内容