美文网首页
网络编程(04)NSURLSessionDataTask和 断点

网络编程(04)NSURLSessionDataTask和 断点

作者: Rui哥 | 来源:发表于2018-05-25 13:10 被阅读25次

    一 使用NSURLSession在线断点下载大文件

    
    @interface DataTaskViewController ()<NSURLSessionDataDelegate>
    @property (weak, nonatomic) IBOutlet UIProgressView *progressView;
    
    
    @property(nonatomic, strong)NSFileHandle *fileHandle;
    @property(nonatomic, assign)long long totalSize;
    @property(nonatomic, assign)long long currentSize;
    
    
    /** 下载的操作 */
    @property(nonatomic, strong)NSURLSessionDataTask *dataTask;
    @end
    
    @implementation DataTaskViewController
    
    
    - (IBAction)startBtnClick:(UIButton *)sender{
        NSLog(@"开始下载----");
       
       self.dataTask = [self startDownLoadTaskWithUrl:@"http://127.0.0.1/MyVideo/bigFile.zip"];
        [self.dataTask resume];
    }
    - (IBAction)pauseBtnClick:(UIButton*)sender{
          NSLog(@"暂停下载----");
        /** 注意如果调用的几次 suspend暂停,那么需要对应的调用 几次resume才能 恢复下载
         */
        [self.dataTask suspend];
        
    }
    - (IBAction)cancleBtnClick:(UIButton*)sender{
        NSLog(@"取消下载----");
        /** 注意; cancle task 后会回调代理方法 完成请求
         -(void)URLSession: task:  didCompleteWithError:
         这个时候最好清空task
         */
        [self.dataTask cancel];
        self.dataTask = nil;
        self.fileHandle = nil;
        
    }
    - (IBAction)resumeBtnClick:(UIButton*)sender{
        NSLog(@"恢复下载----");
        
        /** 注意: 每次发送请求 调用resume方法后都会回调代理方法
         -(void)URLSession:  dataTask:  didReceiveResponse:  completionHandler:
         */
        [self.dataTask resume];
        
    }
    
    
    
    
    -(NSURLSessionDataTask *)startDownLoadTaskWithUrl:(NSString *)urlStr{
        
        // 1 创建URL
        NSURL *url = [NSURL URLWithString:urlStr];
        
        //2. 创建请求
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        // POST 请求才设置
        //    //3. 设置请求的方式
        //    request.HTTPMethod = @"POST";
        //    //4. 设置请求体
        //    request.HTTPBody =data;
        
        // 设置请求头信息,告诉请求的文件的范围 :@"Range"
        // Range value 的书写规范:
        // bytes=起始点-长度   ,开始点和起始点,可以一个不写 , 注意里面不能包含空格
        // bytes=-100  // 表示从起点到100字节的长度
        // bytes=400-1000 //表示从400 开始请求100 的长度
        // bytes=400- //表示从400 开始取完后面的长度
    //    [request setValue:[NSString stringWithFormat:@"bytes=%zd-",self.currentSize] forHTTPHeaderField:@"Range"];
        
        //5. 创建sesstion
        NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                                                              delegate:self
                                                         delegateQueue:[[NSOperationQueue alloc] init]];
        //6. 创建下载任务
        NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
       
        return dataTask;
        
    }
    
    
    
    #pragma mark- NSURLSessionDataDelegate
    //1. 接收到响应头就会响应
    -(void)URLSession:(NSURLSession *)session
             dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveResponse:(NSURLResponse *)response
    completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
        /** 说明文字
         NSHTTPURLResponse ->
         "Content-Length" =     ( 2697307462 );
         "Content-Range" =     ( "bytes 20000-2697327461/2697327462"  );
         "Content-Length" =     ( 2697327462  );
         
         "Content-Length" =     (
         2697297462
         );
         "Content-Range" =     (
         "bytes 30000-2697327461/2697327462"
         );
         */
        
        //1. 获取文件建议名称
        NSString *suggestName = [response suggestedFilename]; // 文件建议的名称,URL 的最后一个节点
        NSString *cachepath = @"/Users/yang/Desktop/testDoc";
        NSString *fullPath = [NSString stringWithFormat:@"%@/%@",cachepath,suggestName];
        
        if ( [[NSFileManager defaultManager] fileExistsAtPath:fullPath]) {
          NSDictionary *fileDic =[[NSFileManager defaultManager] fileAttributesAtPath:fullPath traverseLink:YES];
            NSLog(@"fileDic : %@",fileDic  );
           long long fileSize = [fileDic[@"NSFileSize"]  longLongValue];
            
            if (self.currentSize != fileSize) { //说明文件有问题
                //创建一个空的文件
                [[NSFileManager defaultManager] createFileAtPath:fullPath contents:nil attributes:nil];
            }
        }
        else{
            //创建一个空的文件
            [[NSFileManager defaultManager] createFileAtPath:fullPath contents:nil attributes:nil];
            
        }
        
        
        //2. 创建文件句柄
        /** 注意: 文件句柄 默认指向文件的开头 */
        self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:fullPath];
        [self.fileHandle seekToEndOfFile];// 保证每次都从文件的末尾开始拼接
        
        
        //2. 获取本次请求下载文件总大小
        self.totalSize = [response expectedContentLength];// 这种方法只有在内有设置过Range的时候才准确
        
        /**
         NSURLSessionResponseCancel         // 取消请求
         NSURLSessionResponseAllow          //  接收数据
         NSURLSessionResponseBecomeDownload //
         NSURLSessionResponseBecomeStream   //
         */
        completionHandler(NSURLSessionResponseAllow);
        
    }
    
    //2. 接收到数据就会调用
    -(void)URLSession:(NSURLSession *)session
             dataTask:(NSURLSessionDataTask *)dataTask
       didReceiveData:(NSData *)data{
        
        // 不断的拼接服务器返回的数据
        [self.fileHandle writeData:data];
        self.currentSize += data.length;
        
        CGFloat progress = 1.0 *self.currentSize / self.totalSize;
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.progressView.progress = progress;
        }];
        
        NSLog(@"当前下载进度: %f",progress);
        
    }
    
    //3. 下载完成或者是失败就会调用
    -(void)URLSession:(NSURLSession *)session
                 task:(nonnull NSURLSessionTask *)task
    didCompleteWithError:(nullable NSError *)error{
        
        
        NSLog(@"完成 error: %@",error);
        /** 说明文字
         Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLStringKey=http://127.0.0.1/MyVideo/bigFile.zip, NSLocalizedDescription=cancelled, NSErrorFailingURLKey=http://127.0.0.1/MyVideo/bigFile.zip}
         error - code: -999
         
         */
        if (error == nil) {
            // 得到请求的响应头信息
            self.currentSize = 0;
            [self.fileHandle closeFile];
        }
        
    }
    
    @end
    

    二 使用NSURLSession 断线下载大文件注意事项

    1> 容易出现内存飙升的问题, 我们的解决方案是引入 NSFileHandle 直接将每次的请求下来的一小段数据写入磁盘文件.
    2> 监听文件的下载进度不准确主要是获取下载文件大小的方式不对: 已经下载的文件长度/下载文件总长度
    3> 判断文件的总大小是有个注意点,设置了HTTP请求头Range 和不设置,在响应头里信息会不一样
     "Content-Length" =     ( 2697307462 );
     "Content-Range" =     ( "bytes 20000-2697327461/2697327462"  ); // 这个设置range才会有
    4> response 中的 expectedContentLength 并不是文件的总大小,而是此次请求将要下载的文件的大小.
    5> 断点下载文件完成后文件不完整的解决方法
         - 首先在拼接时要判断该文件是否已经存在 
         - 其次,如果存在还要判断此次接收到的数据是否正式上次的后面,如是,将fileHandle直接移动到文件的末尾,直接拼接即可,如果不是,就是文件的断点下载和上次不连续 终止下载.
         - 如果上次文件不存在,直接创建一个空文件,直接拼接文件
    
    

    三 使用NSURLSession 离线 断点下载大文件

    相关文章

      网友评论

          本文标题:网络编程(04)NSURLSessionDataTask和 断点

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