美文网首页Objective-CiOS技术点
【iOS】文件下载管理器(上)

【iOS】文件下载管理器(上)

作者: Colleny_Z | 来源:发表于2017-10-29 15:39 被阅读24次

    序言

    在做项目的时候经常会用到单文件下载或者批量文件的下载,并且需要实现断点续传,状态变更,进度回调等逻辑。本篇为上文,先实现单文件的下载与状态变更。若需要批量下载,只需要在此基础上创建多个下载器关联即可,下篇文章会详细再写到。

    为何选用NSURLSession?

    我们选择使用NSURLSession实现完成下载。

    原因:苹果已经放弃使用NSURLConnection,而推出更强大的NSURLSession,目前支持最低 iOS 7.0 系统,便于以后拓展维护。

    接下来我们开始进入正题。

    实现个单文件下载器

    1. 我们首先创建NSObject的子类ZXDownLoader.h,并且自定义一个较为便捷的方法,调用此方法开启下载。
    • ZXDownLoader.h中
    - (void)zx_downLoadWithURL:(NSURL *)URL downloadStateChange:(ZXDownLoadStateChangeBlock)stateChange progressBlock:(ZXProgressCompleBlock)progress successed:(ZXDownFinishedBlock)success failed:(ZXDownFailedBlock)failed;
    
    

    下载过程中的,文件的的状态,进度,结果等我们选择使用block进行回调。当然,也可以选择代理或者通知,看自己喜好。

    typedef enum : NSUInteger {
        ZXDownloadStateNormal = 1,
        ZXDownloadStatePause = 2, //暂停
        ZXDownloadStateDowning, //下载中
        ZXDownloadStateSuccess, //成功
        ZXDownloadStateFailed //失败
    } ZXDownloadStateState;
    
    typedef enum : NSUInteger {
        ZXDownFailedCodeNetError = 0,
    } ZXDownFailedErrorCode;
    
    typedef void(^ZXProgressCompleBlock)(long long totalSize,  long long currentSize, CGFloat progress, CGFloat speed);
    typedef void(^ZXDownLoadStateChangeBlock)(ZXDownloadStateState state);
    typedef void(^ZXDownFinishedBlock)(NSString *downFinishedPath);
    typedef void(^ZXDownFailedBlock)(ZXDownFailedErrorCode code);
    

    下载数据初始化,详细步骤可以看以下代码注释

    • ZXDownLoader.m中
    - (void)zx_downLoadWithURL:(NSURL *)URL downloadStateChange:(ZXDownLoadStateChangeBlock)stateChange progressBlock:(ZXProgressCompleBlock)progress successed:(ZXDownFinishedBlock)success failed:(ZXDownFailedBlock)failed{
        self.progressCompleteBlock = progress;
        self.downLoadStateChangeBlock = stateChange;
        self.downFailedBlock = failed;
        self.downFinishedBlock = success;
        
        [self zx_downLoadWithURL:URL];
    }
    
    - (void)zx_downLoadWithURL:(NSURL *)URL{
         //安全判断
        if (URL.absoluteString.length ==0) {
            return;
        }
        
        //文件临时路径与最终存储的路径
        self.tempDownPath = tmpPath(URL.lastPathComponent);
        self.finishDownPath = dscPath(URL.lastPathComponent);
        
        //判断该文件是否已经下载完成,若下载完成,直接return
        if ([ZXFileTools fileExist:self.finishDownPath]) {
            self.task_state = ZXDownloadStateSuccess;
            return;
        }
        
        //判断该文件是否已经正在执行中,若文件状态为暂停 ,则继续下载,若正在下载,则return
        if ([URL isEqual:self.dataTask.originalRequest.URL]) {
            
            if (self.task_state == ZXDownloadStateDowning) {
                return;
            }
           
            if(self.task_state == ZXDownloadStatePause){
                [self zx_resumeCurrentTask];
                return;
            }
        }
         //其它情况,最好取消之前的任务,再取出临时路中的文件,以实现断点续传
        [self zx_cancelTask];
        
        if ([ZXFileTools fileExist:self.tempDownPath]) {
            self.downLoadedFileSize = [ZXFileTools fileSize:self.tempDownPath];
        }
        
        [self downLoadURL:URL Offset:self.downLoadedFileSize];
    }
    - (void)downLoadURL:(NSURL *)URL Offset:(long long)offSet{
        self.task_URL = URL;
       // 表示请求不超时
        NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:URL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:0];
        // 设置请求求信息,然后开启下载任务
        [request setValue:[NSString stringWithFormat:@"byte=%lld-",self.downLoadedFileSize] forHTTPHeaderField:@"Range"];
        self.dataTask = [self.session dataTaskWithRequest:request];
        [self zx_resumeCurrentTask];
    }
    
    1. 根据NSURLSession的代理回调,进行文件下载器属性的更改,状态变化 ,数据的计算,存储与本地化。
    • 文件真正下载开始之前,此代理会在接收数据之前优先做出响应,末尾的block需要我们自己主动传参,告诉session是否允许开始下载。
      注意,在这个代理中,我们可以拿到很多关于下载文件的信息
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveResponse:(NSHTTPURLResponse *)response
     completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{
        self.totalFileSize =  response.expectedContentLength + self.downLoadedFileSize;
        //如若文件与实际文件大小不符,直接删除本地缓存,重新执行下载。
        if (self.downLoadedFileSize > self.totalFileSize) {
            [ZXFileTools removeFile:self.tempDownPath];
            completionHandler(NSURLSessionResponseCancel);
            [self zx_downLoadWithURL:response.URL];
            return;
        }
        
        if (self.downLoadedFileSize == self.totalFileSize) {
            [ZXFileTools moveFileFromPath:self.tempDownPath toPath:self.finishDownPath];
             completionHandler(NSURLSessionResponseCancel);
             self.task_state = ZXDownloadStateSuccess;
            return;
        }
        
        completionHandler(NSURLSessionResponseAllow);
        
        //这里可以初始化输出流,将文件写入本地指定的目录中,并更改文件的下载状态
        self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.tempDownPath append:YES];
        [self.outputStream open];
        self.task_state = ZXDownloadStateDowning;
    }
    
    
    • 文件开启下载,此方法在下载过程会多次回调。
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
        
        self.downLoadedFileSize += data.length;
        //下载器速度计算
        [self caculatorDownloadSpeed:data.length];
        //进度回调计算
        [self caculatorDownloadProgress];
        //数据本地化写入
        [self.outputStream write:data.bytes maxLength:data.length];
    }
    
    - (void)caculatorDownloadSpeed:(long long)dataLenth{
        _timeDurationTotalDownFileSize += dataLenth;
        if (_lastDate == nil) {
            _lastDate = [NSDate date];
        }else{
            NSDate *currentDate = [NSDate date];
            NSTimeInterval  timeDuration = [currentDate timeIntervalSinceDate:_lastDate];
            if(timeDuration >= kDownloadingSpeedDuration){
                self.task_downloading_speed = 1.0 * _timeDurationTotalDownFileSize / kDownloadingSpeedDuration;
                _timeDurationTotalDownFileSize = 0;
                _lastDate = currentDate;
            }
        }
    }
    
    - (void)caculatorDownloadProgress{
         self.task_progress_value = 1.0 * self.downLoadedFileSize / self.totalFileSize;
    }
    
    
    • 下载任务完成,或者异常状态会调用此代理。注意:此方法被调用,并不一定表示下载完成。只是表示此次下载任务终结,比如断网,或者暂停,或者下载完成,或者下载失败等,我们可以根据状态error码来进行具体的判断
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
    didCompleteWithError:(nullable NSError *)error{
     //error为空,且下载长度等同于期望的总长度表示下载完成(严格意义上是不准确的,如果有条件,我们可以凭借文件的md5等来判定。但是我们这里当前只凭借文件长度来判断文件的完整性,)
        if (error == nil) {
            [ZXFileTools moveFileFromPath:self.tempDownPath toPath:self.finishDownPath];
             self.task_state = ZXDownloadStateSuccess;
        }else{
            if (error.code == -999) {
                 self.task_state = ZXDownloadStateNormal;
            }else{
                
                self.task_state = ZXDownloadStateFailed;
                NSLog(@"失败了----%@---%ld",error.localizedDescription,error.code);
                if (self.downFailedBlock) {
                    self.downFailedBlock(ZXDownFailedCodeNetError);
                }
            }
        }
        //下载完成,关闭输出流。下次下载,上面的下载开始前会再次打开,所以不用担心。
        [self.outputStream close];
        self.outputStream = nil;
    }
    
    
    1. 下载的结果,状态,我们统一在进度,状态的set方法里进行blockr的回调处理。
    - (void)setTask_progress_value:(CGFloat)task_progress_value{
        _task_progress_value = task_progress_value;
        
        if (self.progressCompleteBlock) {
            self.progressCompleteBlock(self.totalFileSize,self.downLoadedFileSize,task_progress_value,self.task_downloading_speed);
        }
    }
    
    - (void)setTask_state:(ZXDownloadStateState)task_state{
        if (_task_state == task_state) {
            return;
        }
        _task_state = task_state;
    
        if (self.downLoadStateChangeBlock) {
            self.downLoadStateChangeBlock(task_state);
        }
        
        if (task_state == ZXDownloadStateSuccess) {
            if (self.downFinishedBlock) {
                self.downFinishedBlock(self.finishDownPath);
            }
        }
    }
    
    

    经过以上三步,下载器就算是已经基本完成了。使用的时候,我们直接传入对应的参数就可以直接进行下载了,并完成相应的参数回调。

    结束语

    本篇只是简单的一个文件下载器,下篇我们以此为基础实现多文件批量下载。完整demo,下篇会附上,如有错误,欢迎指出。

    相关文章

      网友评论

        本文标题:【iOS】文件下载管理器(上)

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