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