网络编程(04)NSURLSessionDataTask和 断点下载

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


@interface DataTaskViewController ()
@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和 断点下载)