iOS开发网络多线程之断点下载
一. 断点下载文件
1. 实现功能
点击"开始下载" -> 开始下载文件,进度条实时变化
点击"暂停下载" -> 暂停下载,进度条停止走动
点击"恢复下载" -> 接着上一次下载进度下载
将正在下载或在暂停没有下载完时,将APP关闭,在重新打开时,进度条现在之前的下载进度,点击开始下载,接着上次下载
2. 思路
1> 要实现断点下载,需要将已下载文件存放到cache缓存中,且把文件总大小存放到cache缓存中,程序每次启动,获取沙盒中已下载文件的大小和文件中大小,拿出这两个值之后就可以设置进度条的进度
2> 点击开始按钮:
创建一个NSURLSession会话对象,设置代理
创建一个NSURLSessionDataTask任务发送请求,创建请求时设置请求头部信息,声明文件从哪里开始下载
3> 实现代理方法
在接收服务器响应方法中获取到当前下载文件的大小,并以字典形式写入到沙盒缓存中,创建输出流用来追加写入数据到沙盒中
在接收数据方法中,输出流对象调用写入方法将接收到的数据写入到沙盒中
在完成数据接收方法中,将关闭输出流对象,并将其指针为nil
3. 实现代码
1> 点击开始,开启任务
// 开始下载
- (IBAction)start {
[self.dataTask resume];
}
2> 点击暂停,暂停下载
// 暂停下载
- (IBAction)suspond {
[self.dataTask suspend];
}
3> 点击恢复,恢复下载
// 恢复下载
- (IBAction)recover {
[self.dataTask resume];
}
4> 创建session会话,并设置代理
- (NSURLSession *)session
{
if (_session == nil) {
// 1.创建session会话
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
}
return _session;
}
5> 创建NSURLSessionDataTask任务
请求头部信息中声明从哪里开始下载
- (NSURLSessionDataTask *)dataTask
{
if (_dataTask == nil) {
// 下载完成直接返回,不发送网络请求
NSLog(@"curSize = %zd --- %zd", self.curSize, [self getTotalSize]);
if (self.curSize == [self getTotalSize] && self.curSize != 0) {
NSLog(@"下载完成");
return nil;
}
// 2.创建dataTask任务
/*
表示头500个字节:Range: bytes=0-499
表示第二个500字节:Range: bytes=500-999
表示最后500个字节:Range: bytes=-500
表示500字节以后的范围:Range: bytes=500-
*/
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_04.mp4"];
// 创建请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *range = [NSString stringWithFormat:@"bytes=%zd-", self.curSize];
[request setValue:range forHTTPHeaderField:@"Range"];
_dataTask = [self.session dataTaskWithRequest:request];
}
return _dataTask;
}
6> 代理方法实现
在接收服务器响应方法中获取到当前下载文件的大小,并以字典形式写入到沙盒缓存中,创建输出流用来追加写入数据到沙盒中
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
// 如何处理服务器返回的数据
completionHandler(NSURLSessionResponseAllow);
self.totalSize = response.expectedContentLength + self.curSize;
// 将文件数据的大小保存到沙盒中
// 取出沙盒中保存数据大小的字典
NSString *totalSizePath = [NSString stringGetCacheFullPathToWithFileName:KtotalSize];
NSMutableDictionary *dictM = [NSMutableDictionary dictionaryWithContentsOfFile:totalSizePath];
if (dictM == nil) { // 如果没有初始化字典
dictM = [NSMutableDictionary dictionary];
}
// 给字典赋值
dictM[KfileName] = @(self.totalSize);
// 再将字典回写到沙盒中
[dictM writeToFile:totalSizePath atomically:YES];
// 拼接沙盒路径
NSString *filePath = [NSString stringGetCacheFullPathToWithFileName:KfileName];
self.filePath = filePath;
// 创建输出流
NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:filePath append:YES];
self.stream = stream;
[stream open];
}
在接收数据方法中,输出流对象调用写入方法将接收到的数据写入到沙盒中
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
NSLog(@"didReceiveData");
NSLog(@"%zd", data.length);
// 已下载数据的大小
self.curSize += data.length;
// 下载进度
self.progressView.progress = 1.0 * self.curSize / self.totalSize;
self.label.text = [NSString stringWithFormat:@"%f", 1.0 * self.curSize / self.totalSize];
// 数据存放到本地
[self.stream write:data.bytes maxLength:data.length];
}
在完成数据接收方法中,将关闭输出流对象,并将其指针为nil
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
NSLog(@"didCompleteWithError");
[self.stream close];
self.stream = nil;
}
7> 程序关闭在打开现在当前进度
- (void)viewDidLoad {
[super viewDidLoad];
// 1.获取沙盒中已下载文件的大小
NSString *filePath = [NSString stringGetCacheFullPathToWithFileName:KfileName];
NSLog(@"%@", filePath);
NSFileManager *manager = [NSFileManager defaultManager];
NSDictionary *fileDict = [manager attributesOfItemAtPath:filePath error:nil];
self.curSize = [fileDict[@"NSFileSize"] integerValue];
NSLog(@"%zd", self.curSize);
// 2.获取沙盒中数据总大小
self.totalSize = [self getTotalSize];
if (self.totalSize == 0) {
NSLog(@"第一次下载");
} else {
self.progressView.progress = 1.0 * self.curSize / self.totalSize;
}
}
获取沙盒中文件总大小
// 获取沙盒中文件总大小
- (NSInteger)getTotalSize
{
NSString *totalSizePath = [NSString stringGetCacheFullPathToWithFileName:KtotalSize];
NSLog(@"%@", totalSizePath);
NSMutableDictionary *dictM = [NSMutableDictionary dictionaryWithContentsOfFile:totalSizePath];
NSLog(@"%@", dictM);
if (dictM == nil) {
return 0;
} else {
NSLog(@"------%zd", [dictM[KfileName] integerValue]);
return [dictM[KfileName] integerValue];
}
}
8> 凡创建的会话都需要在dealloc中释放内存
- (void)dealloc
{
// 会话释放
[self.session invalidateAndCancel];
}