实现项目下载需求时遇过的那些坑

2017-01-20  本文已影响37人  有梦想的老伯伯
现假定我们的需求是最常见,也是最能体现技术问题的一个,叫做:
我们先来看下实现队列下载、断点续传等需求的关键示例代码:
    NSError * error = nil;
    
    // 创建下载队列
    NSOperationQueue * downloadOperationQueue = [[NSOperationQueue alloc]init];
    //  规定operationQueue中,最大可以同时执行的operation数量为1
    downloadOperationQueue.maxConcurrentOperationCount = 1;
    
    // 创建单个下载任务(访问已下载部分的文件,实现断点续传)
    NSMutableURLRequest * downloadRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:DOWNLOAD_URL_STRING]];
    [[NSURLCache sharedURLCache] removeCachedResponseForRequest:downloadRequest];
    
    AFHTTPRequestOperation * downloadOperation = [[AFHTTPRequestOperation alloc]initWithRequest:downloadRequest];
    
    unsigned long long downloadedPartFileSize = 0;
    
    if ([[NSFileManager defaultManager] fileExistsAtPath:DOWNLOADED_PART_FILE_PATH]) {
        NSDictionary * fileAttributes = [[NSFileManager defaultManager]attributesOfItemAtPath:DOWNLOADED_PART_FILE_PATH error:&error];
        downloadedPartFileSize = [fileAttributes fileSize];
        NSString * headerRangeFieldValue = [NSString stringWithFormat:@"bytes=%llu-", downloadedPartFileSize];
        [downloadRequest setValue:headerRangeFieldValue forHTTPHeaderField:@"Range"];
    }
    
    downloadOperation.outputStream = [NSOutputStream outputStreamToFileAtPath:DOWNLOADED_PART_FILE_PATH append:YES];
    
    [downloadOperation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
        NSLog(@"%lld/%lld", totalBytesRead + downloadedPartFileSize, totalBytesExpectedToRead + downloadedPartFileSize);
    }];
    
    [downloadOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSLog(@"downloadOperation completion block invoked");
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"downloadOperation failure block invoked");
    }];
    
    //  将单个下载任务加入到下载队列当中
    [downloadOperationQueue addOperation:downloadOperation];
    
    //  暂停某下载任务
    [downloadOperation pause];
    
    //  继续某下载任务
    [downloadOperation resume];
    
    //  取消某下载任务(同时应将其已下载部分的文件删除)
    [downloadOperation cancel];
    [[NSFileManager defaultManager] removeItemAtPath:DOWNLOADED_PART_FILE_PATH error:&error];
    
    //  取消全部下载任务
    [downloadOperationQueue cancelAllOperations];
    
    //  此外还有若干方法用以判断相应一见其名便知其义的状态...
    downloadOperation.isReady
    downloadOperation.isExecuting
    downloadOperation.isPaused
    downloadOperation.isCancelled
    downloadOperation.isFinished
    
    //  判断downloadOperation是否存在在downloadOperationQueue当中
    [downloadOperationQueue.operations containsObject:downloadOperation]

错!!!

惊人事实 1: 对queue中前一个下载operation执行pause方法,下一个operation并不能自动启动进入正在执行的状态!!

惊人事实 2: 如果queue中前一个下载operation执行失败了(可用下载中途断网进行模拟),它将从queue中自动地被移除掉!!

惊人事实 3: 注意到代码里那个failure回调的block了没?它不仅仅将在operation执行失败的时候被调用,还会在operation被cancel的时候被调用!!所以对于神马叫做“operation的失败”,你要重新建立起你的世界观了!!

惊人事实 4: 如果对一个正处于pause状态的operation执行cancel会怎么样?答案是这个operation还保留在queue中!!并且仍然保持着pause状态!!仅有的一点变化,是它的isCancelled属性,变成了YES!!

惊人事实 5:如果一个queue中有一个下载operation正在执行,此时对另一处在isReady状态的operation执行start方法会怎么样?你很可能会说:“没用的,因为之前设了queue.maxConcurrentOperationCount = 1嘛!” 可事实恰好相反,这个operation也会立刻被启动执行!!于是乎你不忍心看到的事情就出现了,这时queue将会有两个任务被同时执行!!maxConcurrentOperationCount完全失效了!!

惊人事实 6:承接上一点,如果此时另一条的状态不是isReady,而是isPaused暂停状态,你对其执行resume方法,此时会怎么样呢?哈哈,没错,你吸取了上一条的经验,终于猜对了!这个operation也会立刻启动被执行,不管当前的queue有没有另一个operation正在被执行!!从中我们就可以意识到,maxConcurrentOperationCount这个属性,只能管得自动启动每一operation时,先检查下是否正在执行的operation的数量已经超过那个数字了;可是如果你要手动start某一operation,对不起,这条限制半点都没有用处了......

惊人事实 7:从上表中我们可以看到,无论是一个operation自然地执行完毕,还是中途失败,还是被执行了cancel方法,都会被标记为isFinished,从operation中被移除掉,operation所认为的“完成”可完全不像我们想象中的那么狭义!问题来了,此时如果再对这个operation执行start方法会怎么样?对不起!没有任何用处!😭
所以你如果想要让一个已失败的operation从断点处继续再开始执行下载该怎么办?不好意思,只好新建operation重新再来了......

不能够使用NSOperationQueue来进行多下载任务的管理!!!

理由如下:

......自己去体会吧,反正坑多的已经无力吐槽,再坚持下去也是枉费心思了。

在使用AFHTTPRequestOperation时我们还需要注意以下几点:

转载请注明出处有梦想的老伯伯

上一篇 下一篇

猜你喜欢

热点阅读