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

一 使用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 离线 断点下载大文件

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

推荐阅读更多精彩内容