[iOS 多线程 & 网络 - 2.4] - 大文件下载 (边下边写/暂停恢复下载/压缩解压zip/多线程下载)
A.需求
- 边下边写入硬盘
- 显示下载进度
- 暂停/恢复 下载
- 解压文件
- 多线程下载
B.基本知识
1.小文件下载
如果文件比较小,下载方式会比较多
直接用NSData的+ (id)dataWithContentsOfURL:(NSURL *)url;
利用NSURLConnection发送一个HTTP请求去下载
如果是下载图片,还可以利用SDWebImage框架
直接用NSData的+ (id)dataWithContentsOfURL:(NSURL *)url;
利用NSURLConnection发送一个HTTP请求去下载
如果是下载图片,还可以利用SDWebImage框架
2.HTTP的Range头信息
通过设置请求头Range可以指定每次从网路下载数据包的大小
Range示例
bytes=0-499 从0到499的头500个字节
bytes=500-999 从500到999的第二个500字节
bytes=500- 从500字节以后的所有字节
bytes=-500 最后500个字节
bytes=500-599,800-899 同时指定几个范围
Range小结
- 用于分隔
前面的数字表示起始字节数
后面的数组表示截止字节数,没有表示到末尾
, 用于分组,可以一次指定多个Range,不过很少用
Range示例
bytes=0-499 从0到499的头500个字节
bytes=500-999 从500到999的第二个500字节
bytes=500- 从500字节以后的所有字节
bytes=-500 最后500个字节
bytes=500-599,800-899 同时指定几个范围
Range小结
- 用于分隔
前面的数字表示起始字节数
后面的数组表示截止字节数,没有表示到末尾
, 用于分组,可以一次指定多个Range,不过很少用
3.第三方解压框架SSZipArchive
下载地址:https://github.com/samsoffes/ssziparchive
注意:需要引入libz.dylib框架
注意:需要引入libz.dylib框架
1 // Unzipping 2 NSString *zipPath = @"path_to_your_zip_file"; 3 NSString *destinationPath = @"path_to_the_folder_where_you_want_it_unzipped"; 4 [SSZipArchive unzipFileAtPath:zipPath toDestination:destinationPath]; 5 6 // Zipping 7 NSString *zippedPath = @"path_where_you_want_the_file_created"; 8 NSArray *inputPaths = [NSArray arrayWithObjects: 9 [[NSBundle mainBundle] pathForResource:@"photo1" ofType:@"jpg"], 10 [[NSBundle mainBundle] pathForResource:@"photo2" ofType:@"jpg"] 11 nil]; 12 [SSZipArchive createZipFileAtPath:zippedPath withFilesAtPaths:inputPaths];
C.实现
1.使用NSFileHandle实现边下边写入硬盘
1 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { 2 NSLog(@"开始接收"); 3 4 // 获取存放路径 5 NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 6 NSString *filePath = [cachePath stringByAppendingPathComponent:@"0627.zip"]; 7 8 // 创建一个空的文件,用来存放接收的数据 9 NSFileManager *manager = [NSFileManager defaultManager]; 10 [manager createFileAtPath:filePath contents:nil attributes:nil]; 11 12 // 把文件句柄用写入方式指向文件 13 self.handle = [NSFileHandle fileHandleForWritingAtPath:filePath]; 14 15 } 16 17 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { 18 NSLog(@"正在接收: %d byte", data.length); 19 20 // 移动句柄到上次写入数据的末位置 21 [self.handle seekToEndOfFile]; 22 23 // 写入数据 24 [self.handle writeData:data]; 25 }
2.显示下载进度条
使用ProgressView
从response得到文件大小,再累加每次接收到的数据,计算出完成百分比
1 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { 2 NSLog(@"开始接收"); 3 4 // 获取存放路径 5 NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 6 NSString *filePath = [cachePath stringByAppendingPathComponent:@"0627.zip"]; 7 8 // 创建一个空的文件,用来存放接收的数据 9 NSFileManager *manager = [NSFileManager defaultManager]; 10 [manager createFileAtPath:filePath contents:nil attributes:nil]; 11 12 // 把文件句柄用写入方式指向文件 13 self.handle = [NSFileHandle fileHandleForWritingAtPath:filePath]; 14 15 // 复位进度条 16 self.progressView.progress = 0.0; 17 18 // 复位当前下载量 19 self.currentDataLength = 0; 20 21 // 得到总的文件大小 22 self.totalDataLength = response.expectedContentLength; 23 24 25 } 26 27 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { 28 NSLog(@"正在接收: %d byte", data.length); 29 // 移动句柄到上次写入数据的末位置 30 [self.handle seekToEndOfFile]; 31 32 // 写入数据 33 [self.handle writeData:data]; 34 35 // 下载量累加 36 self.currentDataLength += data.length; 37 38 // 刷新进度条 39 CGFloat completedProgress = (double)self.currentDataLength / self.totalDataLength; 40 NSLog(@"completed percentage: %f%%", completedProgress * 100); 41 self.progressView.progress = completedProgress; 42 }
3.暂停/恢复下载
使用成员属性标记下载状态
请求得到文件头信息,根据当前下载量,设置请求request头信息,从上次下载完成的地方开始继续下载
注意:NSURLConnection 取消cancel之后不能恢复,只能再创建
1 // 2 // BigFileDownloadViewController.m 3 // BigFileDownloadDemo 4 // 5 // Created by hellovoidworld on 15/1/26. 6 // Copyright (c) 2015年 hellovoidworld. All rights reserved. 7 // 8 9 #import "BigFileDownloadViewController.h" 10 11 @interface BigFileDownloadViewController () 12 13 /** 进度条 */ 14 @property (weak, nonatomic) IBOutlet UIProgressView *progressView; 15 16 /** 文件句柄 */ 17 @property(nonatomic, strong) NSFileHandle *handle; 18 19 /** 当前完成的下载量 */ 20 @property(nonatomic, assign) long long currentDataLength; 21 22 /** 总的文件大小 */ 23 @property(nonatomic, assign) long long totalDataLength; 24 25 /** 当前下载连接 */ 26 @property(nonatomic, strong) NSURLConnection *conn; 27 28 /** 下载状态标识 */ 29 @property(nonatomic, assign, getter=isDownloading) BOOL downloading; 30 31 /** 下载/暂停 按钮事件 */ 32 - (IBAction)download:(UIButton *)button; 33 34 @end 35 36 @implementation BigFileDownloadViewController 37 38 - (void)viewDidLoad { 39 [super viewDidLoad]; 40 // Do any additional setup after loading the view. 41 } 42 43 44 - (IBAction)download:(UIButton *)button { 45 if (self.isDownloading) { // 如果正在下载中,暂停下载 46 47 self.downloading = NO; 48 [button setTitle:@"开始" forState:UIControlStateNormal]; 49 50 // 取消连接,不能恢复 51 [self.conn cancel]; 52 self.conn = nil; 53 } else { // 如果是暂停中,恢复下载 54 55 self.downloading = YES; 56 [button setTitle:@"暂停" forState:UIControlStateNormal]; 57 58 // 从上次下载完成的地方继续下载,初始就是0 59 NSURL *url = [NSURL URLWithString:@"http://192.168.0.21:8080/MyTestServer/videos/0627.zip"]; 60 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 61 62 // 设置request头信息,指明要从文件哪里开始下载 63 NSString *value = [NSString stringWithFormat:@"bytes=%lld-", self.currentDataLength]; 64 [request setValue:value forHTTPHeaderField:@"Range"]; 65 66 self.conn = [NSURLConnection connectionWithRequest:request delegate:self]; 67 } 68 } 69 70 #pragma mark - NSURLConnectionDataDelegate 71 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { 72 NSLog(@"失败"); 73 NSLog(@"%@", error); 74 } 75 76 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { 77 NSLog(@"开始接收"); 78 79 // 如果是继续下载,就不要再重复创建文件了 80 if (self.totalDataLength) return; 81 82 // 获取存放路径 83 NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 84 NSString *filePath = [cachePath stringByAppendingPathComponent:@"0627.zip"]; 85 86 // 创建一个空的文件,用来存放接收的数据 87 NSFileManager *manager = [NSFileManager defaultManager]; 88 if ([manager fileExistsAtPath:filePath]) { 89 [manager removeItemAtPath:filePath error:nil]; 90 } 91 92 // 刚创建默认是0字节 93 [manager createFileAtPath:filePath contents:nil attributes:nil]; 94 95 96 // 把文件句柄用写入方式指向文件 97 self.handle = [NSFileHandle fileHandleForWritingAtPath:filePath]; 98 99 // 得到总的文件大小 100 self.totalDataLength = response.expectedContentLength; 101 102 103 } 104 105 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { 106 NSLog(@"正在接收: %d byte", data.length); 107 108 // 下载量累加 109 self.currentDataLength += data.length; 110 111 // 刷新进度条 112 CGFloat completedProgress = (double)self.currentDataLength / self.totalDataLength; 113 NSLog(@"completed percentage: %f%%", completedProgress * 100); 114 self.progressView.progress = completedProgress; 115 116 // 移动句柄到上次写入数据的末位置 117 [self.handle seekToEndOfFile]; 118 119 // 写入数据 120 [self.handle writeData:data]; 121 } 122 123 - (void)connectionDidFinishLoading:(NSURLConnection *)connection { 124 NSLog(@"接收完毕"); 125 126 // 复位当前下载量 127 self.currentDataLength = 0; 128 129 // 复位下载文件大小 130 self.totalDataLength = 0; 131 132 // 关闭连接 133 [self.handle closeFile]; 134 self.handle = nil; 135 } 136 137 138 @end
4.文件解压/压缩
使用ssziparchive-master框架
注意:需要引入libz.dylib框架
(1)解压
使用block存储下载完成后的操作:解压
1 // 完成下载后block 2 self.downloader.didFinishHandler = ^{ 3 // 解压文件 4 [SSZipArchive unzipFileAtPath:filePath toDestination:cachePath]; 5 NSLog(@"解压完成!"); 6 };
#mark: 我使用大小为1.4G,仅包含一个视频的压缩文件进行解压,没有解出来; 解压包含了几张图片的压缩包能正常工作。
(2)压缩
a.准备实验文件
b.使用SSZipArchive归档解压
1 // 获得所有png图片,注意这里是不能获得沙盒中的文件的,只能是项目中的 2 NSArray *pngs = [[NSBundle mainBundle] pathsForResourcesOfType:@"PNG" inDirectory:nil]; 3 NSLog(@"%@", pngs); 4 5 // 压缩文件存放路径 6 NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 7 NSString *filePath = [cachePath stringByAppendingPathComponent:@"pngs.zip"]; 8 9 // 压缩文件 10 [SSZipArchive createZipFileAtPath:filePath withFilesAtPaths:pngs]; 11 12 NSLog(@"压缩完成!");
5.下载器的封装
将下载功能封装成一个类,传入请求url和文件存放地址,调用接口开启、暂停线程
1 // 2 // FileDownloader.h 3 // BigFileDownloadDemo 4 // 5 // Created by hellovoidworld on 15/1/26. 6 // Copyright (c) 2015年 hellovoidworld. All rights reserved. 7 // 8 9 #import <Foundation/Foundation.h> 10 11 @interface FileDownloader : NSObject 12 13 /** 请求路径 */ 14 @property(nonatomic, strong) NSURL *url; 15 16 /** 存放路径 */ 17 @property(nonatomic, strong) NSString *filePath; 18 19 /** 下载状态 */ 20 @property(nonatomic, readonly, getter=isDownloading) BOOL downloading; 21 22 /** 更新进度block */ 23 @property(nonatomic, copy) void(^progressHandler)(double progress); 24 25 /** 完成下载后block */ 26 @property(nonatomic, copy) void(^didFinishHandler)(); 27 28 - (void) startDownloading; 29 - (void) pauseDownloading; 30 31 @end
1 // 2 // FileDownloader.m 3 // BigFileDownloadDemo 4 // 5 // Created by hellovoidworld on 15/1/26. 6 // Copyright (c) 2015年 hellovoidworld. All rights reserved. 7 // 8 9 #import "FileDownloader.h" 10 #import <UIKit/UIKit.h> 11 12 @interface FileDownloader() <NSURLConnectionDataDelegate> 13 14 /** 文件句柄 */ 15 @property(nonatomic, strong) NSFileHandle *handle; 16 17 /** 当前完成的下载量 */ 18 @property(nonatomic, assign) long long currentDataLength; 19 20 /** 总的文件大小 */ 21 @property(nonatomic, assign) long long totalDataLength; 22 23 /** 当前下载连接 */ 24 @property(nonatomic, strong) NSURLConnection *conn; 25 26 @end 27 28 @implementation FileDownloader 29 30 /** 开始下载 */ 31 - (void) startDownloading { 32 _downloading = YES; 33 34 // 从上次下载完成的地方继续下载,初始就是0 35 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.url]; 36 37 // 设置request头信息,指明要从文件哪里开始下载 38 NSString *value = [NSString stringWithFormat:@"bytes=%lld-", self.currentDataLength]; 39 [request setValue:value forHTTPHeaderField:@"Range"]; 40 41 self.conn = [NSURLConnection connectionWithRequest:request delegate:self]; 42 } 43 44 /** 暂停下载 */ 45 - (void) pauseDownloading { 46 _downloading = NO; 47 48 // 取消连接,不能恢复 49 [self.conn cancel]; 50 self.conn = nil; 51 } 52 53 #pragma mark - NSURLConnectionDataDelegate 54 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { 55 NSLog(@"失败"); 56 NSLog(@"%@", error); 57 } 58 59 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { 60 NSLog(@"开始接收"); 61 62 // 如果是继续下载,就不要再重复创建文件了 63 if (self.totalDataLength) return; 64 65 // 创建一个空的文件,用来存放接收的数据 66 NSFileManager *manager = [NSFileManager defaultManager]; 67 if ([manager fileExistsAtPath:self.filePath]) { 68 [manager removeItemAtPath:self.filePath error:nil]; 69 } 70 71 // 刚创建默认是0字节 72 [manager createFileAtPath:self.filePath contents:nil attributes:nil]; 73 74 75 // 把文件句柄用写入方式指向文件 76 self.handle = [NSFileHandle fileHandleForWritingAtPath:self.filePath]; 77 78 // 得到总的文件大小 79 self.totalDataLength = response.expectedContentLength; 80 81 82 } 83 84 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { 85 NSLog(@"正在接收: %d byte", data.length); 86 87 // 下载量累加 88 self.currentDataLength += data.length; 89 90 // 刷新进度条 91 CGFloat completedProgress = (double)self.currentDataLength / self.totalDataLength; 92 NSLog(@"completed percentage: %f%%", completedProgress * 100); 93 if (self.progressHandler) { 94 self.progressHandler(completedProgress); 95 } 96 97 // 移动句柄到上次写入数据的末位置 98 [self.handle seekToEndOfFile]; 99 100 // 写入数据 101 [self.handle writeData:data]; 102 103 NSLog(@"现在的线程:%@", [NSThread currentThread]); 104 } 105 106 - (void)connectionDidFinishLoading:(NSURLConnection *)connection { 107 NSLog(@"接收完毕"); 108 109 // 复位当前下载量 110 self.currentDataLength = 0; 111 112 // 复位下载文件大小 113 self.totalDataLength = 0; 114 115 // 关闭连接 116 [self.handle closeFile]; 117 self.handle = nil; 118 119 if (self.didFinishHandler) { 120 self.didFinishHandler(); 121 } 122 } 123 124 @end
1 // 2 // BigFileDownloadViewController.m 3 // BigFileDownloadDemo 4 // 5 // Created by hellovoidworld on 15/1/26. 6 // Copyright (c) 2015年 hellovoidworld. All rights reserved. 7 // 8 9 #import "BigFileDownloadViewController.h" 10 #import "FileDownloader.h" 11 #import "SSZipArchive.h" 12 13 @interface BigFileDownloadViewController () 14 15 /** 进度条 */ 16 @property (weak, nonatomic) IBOutlet UIProgressView *progressView; 17 18 /** 下载器 */ 19 @property(nonatomic, strong) FileDownloader *downloader; 20 21 /** 下载/暂停 按钮事件 */ 22 - (IBAction)download:(UIButton *)button; 23 24 @end 25 26 @implementation BigFileDownloadViewController 27 28 - (void)viewDidLoad { 29 [super viewDidLoad]; 30 // Do any additional setup after loading the view. 31 32 // 设置下载器请求路径 33 self.downloader = [[FileDownloader alloc] init]; 34 self.downloader.url = [NSURL URLWithString:@"http://192.168.0.21:8080/MyTestServer/videos/0627.zip"]; 35 // self.downloader.url = [NSURL URLWithString:@"http://192.168.0.21:8080/MyTestServer/images/images.zip"]; 36 37 // 设置存放路径 38 NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 39 NSString *filePath = [cachePath stringByAppendingPathComponent:@"0627.zip"]; 40 // NSString *filePath = [cachePath stringByAppendingPathComponent:@"images.zip"]; 41 self.downloader.filePath = filePath; 42 43 44 // 刷新进度条的block 45 typeof(self) vc = self; 46 self.downloader.progressHandler = ^(double progress) { 47 vc.progressView.progress = progress; 48 }; 49 50 // 完成下载后block 51 self.downloader.didFinishHandler = ^{ 52 // 解压文件 53 [SSZipArchive unzipFileAtPath:filePath toDestination:cachePath]; 54 NSLog(@"解压完成!"); 55 }; 56 } 57 58 59 - (IBAction)download:(UIButton *)button { 60 if (self.downloader.isDownloading) { // 如果正在下载中,暂停下载 61 [button setTitle:@"开始" forState:UIControlStateNormal]; 62 [self.downloader pauseDownloading]; 63 } else { // 如果是暂停中,恢复下载 64 [button setTitle:@"暂停" forState:UIControlStateNormal]; 65 [self.downloader startDownloading]; 66 } 67 } 68 69 70 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 71 72 // 获得所有png图片 73 NSArray *pngs = [[NSBundle mainBundle] pathsForResourcesOfType:@"PNG" inDirectory:nil]; 74 NSLog(@"%@", pngs); 75 76 // 压缩文件存放路径 77 NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 78 NSString *filePath = [cachePath stringByAppendingPathComponent:@"pngs.zip"]; 79 80 // 压缩文件 81 [SSZipArchive createZipFileAtPath:filePath withFilesAtPaths:pngs]; 82 83 NSLog(@"压缩完成!"); 84 } 85 86 @end
6.多线程下载
思路:由于调用NSURLConnection的代理方法进行服务器请求和接收,发送的是异步请求,所以可以认为利用几个NSURLConnection同时发送请求,下载一个文件的不同部分,就能实现多线程下载。
- 创建多线下载器类和单线下载器
- 多线下载器对象创建n个单线下载器来进行下载
- 创建一个跟实际文件大小一样的临时文件,用来辅助定位单线下载器的下载存储位置
- n个单线下载器负责一个文件的不同部分的下载
- 使用请求头设置Range来实现文件下载部分的不同
(1)基本下载器
用来声明了一些公用属性和方法
1 // 2 // FileDownloader.h 3 // BigFileDownloadDemo 4 // 5 // Created by hellovoidworld on 15/1/26. 6 // Copyright (c) 2015年 hellovoidworld. All rights reserved. 7 // 8 9 #import <Foundation/Foundation.h> 10 11 @interface FileDownloader : NSObject 12 { 13 @protected 14 BOOL _downloading; 15 } 16 17 /** 请求路径 */ 18 @property(nonatomic, strong) NSString *url; 19 20 /** 存放路径 */ 21 @property(nonatomic, strong) NSString *filePath; 22 23 /** 下载状态 */ 24 @property(nonatomic, readonly, getter=isDownloading) BOOL downloading; 25 26 /** 更新进度block */ 27 @property(nonatomic, copy) void(^progressHandler)(double progress); 28 29 /** 完成下载后block */ 30 @property(nonatomic, copy) void(^didFinishHandler)(); 31 32 - (void) startDownloading; 33 - (void) pauseDownloading; 34 35 @end
(2)单线下载器
1 // 2 // SingleDownloader.h 3 // MultiDownloaderDemo 4 // 5 // Created by hellovoidworld on 15/1/27. 6 // Copyright (c) 2015年 hellovoidworld. All rights reserved. 7 // 8 9 #import "FileDownloader.h" 10 11 @interface SingleDownloader : FileDownloader 12 13 /** 开始下载的位置(字节) */ 14 @property(nonatomic, assign) long long begin; 15 16 /** 结束下载的位置(字节) */ 17 @property(nonatomic, assign) long long end; 18 19 @end
1 // 2 // SingleDownloader.m 3 // MultiDownloaderDemo 4 // 5 // Created by hellovoidworld on 15/1/27. 6 // Copyright (c) 2015年 hellovoidworld. All rights reserved. 7 // 8 9 #import "SingleDownloader.h" 10 11 @interface SingleDownloader() <NSURLConnectionDataDelegate> 12 13 /** 文件句柄 */ 14 @property(nonatomic, strong) NSFileHandle *handle; 15 16 /** 当前下载连接 */ 17 @property(nonatomic, strong) NSURLConnection *conn; 18 19 /** 当前下载量 */ 20 @property(nonatomic, assign) long long currentLength; 21 22 @end 23 24 @implementation SingleDownloader 25 26 /** 初始化文件句柄 */ 27 - (NSFileHandle *)handle { 28 if (nil == _handle) { 29 _handle = [NSFileHandle fileHandleForWritingAtPath:self.filePath]; 30 } 31 return _handle; 32 } 33 34 /** 开始下载 */ 35 - (void) startDownloading { 36 // 从上次下载完成的地方继续下载,初始就是0 37 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]]; 38 39 // 设置request头信息,指明要从文件哪里开始下载 40 NSString *value = [NSString stringWithFormat:@"bytes=%lld-%lld", self.begin, self.end]; 41 [request setValue:value forHTTPHeaderField:@"Range"]; 42 43 self.conn = [NSURLConnection connectionWithRequest:request delegate:self]; 44 45 _downloading = YES; 46 } 47 48 /** 暂停下载 */ 49 - (void) pauseDownloading { 50 // 取消连接,不能恢复 51 [self.conn cancel]; 52 self.conn = nil; 53 54 _downloading = NO; 55 } 56 57 #pragma mark - NSURLConnectionDataDelegate 58 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { 59 NSLog(@"失败"); 60 NSLog(@"%@", error); 61 } 62 63 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { 64 NSLog(@"开始接收"); 65 } 66 67 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { 68 // 移动句柄到上次写入数据的末位置 69 [self.handle seekToFileOffset:self.begin + self.currentLength]; 70 71 // 写入数据 72 [self.handle writeData:data]; 73 74 // 已经下载的量 75 self.currentLength += data.length; 76 77 if (self.progressHandler) { 78 double progress = (double)self.currentLength / (self.end - self.begin + 1); 79 self.progressHandler(progress); 80 } 81 } 82 83 - (void)connectionDidFinishLoading:(NSURLConnection *)connection { 84 NSLog(@"接收完毕"); 85 86 // 清空下载量 87 self.currentLength = 0; 88 89 90 // 关闭连接 91 [self.handle closeFile]; 92 self.handle = nil; 93 94 if (self.didFinishHandler) { 95 self.didFinishHandler(); 96 } 97 } 98 99 100 @end
(3)总下载器
1 // 2 // MultiDownloader.h 3 // MultiDownloaderDemo 4 // 5 // Created by hellovoidworld on 15/1/27. 6 // Copyright (c) 2015年 hellovoidworld. All rights reserved. 7 // 8 9 #import "FileDownloader.h" 10 11 @interface MultiDownloader : FileDownloader 12 13 @end 14 15 // 16 // MultiDownloader.m 17 // MultiDownloaderDemo 18 // 19 // Created by hellovoidworld on 15/1/27. 20 // Copyright (c) 2015年 hellovoidworld. All rights reserved. 21 // 22 23 #import "MultiDownloader.h" 24 #import "SingleDownloader.h" 25 26 #define MaxMultilineCount 4 27 28 @interface MultiDownloader() 29 30 @property(nonatomic, strong) NSMutableArray *singleDownloaders; 31 32 @end 33 34 @implementation MultiDownloader 35 36 /** 初始化单线下载器 */ 37 - (NSMutableArray *)singleDownloaders { 38 if (!_singleDownloaders) { 39 _singleDownloaders = [NSMutableArray array]; 40 41 long long fileSize = [self getFileSize]; 42 long long singleFileSize = 0; // 每条子线下载量 43 if (fileSize % MaxMultilineCount == 0) { 44 singleFileSize = fileSize / MaxMultilineCount; 45 } else { 46 singleFileSize = fileSize / MaxMultilineCount + 1; 47 } 48 49 for (int i=0; i<MaxMultilineCount; i++) { 50 SingleDownloader *downloader = [[SingleDownloader alloc] init]; 51 downloader.url = self.url; 52 downloader.filePath = self.filePath; 53 downloader.begin = i * singleFileSize; 54 downloader.end = downloader.begin + singleFileSize - 1; 55 downloader.progressHandler = ^(double progress){ 56 NSLog(@"%d号单线下载器正在下载,下载进度:%f", i, progress); 57 }; 58 59 [self.singleDownloaders addObject:downloader]; 60 } 61 62 // 创建临时文件,文件大小要跟实际大小一致 63 // 1.创建一个0字节文件 64 [[NSFileManager defaultManager] createFileAtPath:self.filePath contents:nil attributes:nil]; 65 66 // 2.指定文件大小 67 NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.filePath]; 68 [fileHandle truncateFileAtOffset:fileSize]; 69 } 70 71 return _singleDownloaders; 72 } 73 74 /** 获得文件大小 */ 75 - (long long) getFileSize { 76 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]]; 77 request.HTTPMethod = @"HEAD";// 请求得到头响应 78 79 NSURLResponse *response = nil; 80 [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil]; 81 return response.expectedContentLength; 82 } 83 84 /** 开始下载 */ 85 - (void)startDownloading { 86 [self.singleDownloaders makeObjectsPerformSelector:@selector(startDownloading)]; 87 _downloading = YES; 88 NSLog(@"多线程下载开始"); 89 } 90 91 /** 暂停下载 */ 92 - (void)pauseDownloading { 93 [self.singleDownloaders makeObjectsPerformSelector:@selector(pauseDownloading)]; 94 _downloading = NO; 95 NSLog(@"多线程下载暂停!"); 96 } 97 98 @end 99 100 (4)控制器调用 101 - (IBAction)startDownloading { 102 MultiDownloader *downloader = [[MultiDownloader alloc] init]; 103 downloader.url = @"http://192.168.0.21:8080/MyTestServer/videos/0627.zip"; 104 105 // 设置存放路径 106 NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; 107 downloader.filePath = [cachePath stringByAppendingPathComponent:@"0627.zip"]; 108 109 // 开始下载 110 [downloader startDownloading]; 111 }