iOS開發網絡篇—多線程斷點下載下傳
說明:本文介紹多線程斷點下載下傳。項目中使用了蘋果自帶的類,實作了同時開啟多條線程下載下傳一個較大的檔案。因為實作過程較為複雜,是以下面貼出完整的代碼。
實作思路:下載下傳開始,建立一個和要下載下傳的檔案大小相同的檔案(如果要下載下傳的檔案為100M,那麼就在沙盒中建立一個100M的檔案,然後計算每一段的下載下傳量,開啟多條線程下載下傳各段的資料,分别寫入對應的檔案部分)。

完成的實作代碼如下:
主要制器中的代碼:
1 #import "YYViewController.h"
2 #import "YYFileMultiDownloader.h"
3
4 @interface YYViewController ()
5 @property (nonatomic, strong) YYFileMultiDownloader *fileMultiDownloader;
6 @end
7
8 @implementation YYViewController
9 - (YYFileMultiDownloader *)fileMultiDownloader
10 {
11 if (!_fileMultiDownloader) {
12 _fileMultiDownloader = [[YYFileMultiDownloader alloc] init];
13 // 需要下載下傳的檔案遠端URL
14 _fileMultiDownloader.url = @"http://192.168.1.200:8080/MJServer/resources/jre.zip";
15 // 檔案儲存到什麼地方
16 NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
17 NSString *filepath = [caches stringByAppendingPathComponent:@"jre.zip"];
18 _fileMultiDownloader.destPath = filepath;
19 }
20 return _fileMultiDownloader;
21 }
22
23 - (void)viewDidLoad
24 {
25 [super viewDidLoad];
26
27 }
28
29 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
30 {
31 [self.fileMultiDownloader start];
32 }
33
34 @end
自定義一個基類
YYFileDownloader.h檔案
1 #import <Foundation/Foundation.h>
2
3 @interface YYFileDownloader : NSObject
4 {
5 BOOL _downloading;
6 }
7 /**
8 * 所需要下載下傳檔案的遠端URL(連接配接伺服器的路徑)
9 */
10 @property (nonatomic, copy) NSString *url;
11 /**
12 * 檔案的存儲路徑(檔案下載下傳到什麼地方)
13 */
14 @property (nonatomic, copy) NSString *destPath;
15
16 /**
17 * 是否正在下載下傳(有沒有在下載下傳, 隻有下載下傳器内部才知道)
18 */
19 @property (nonatomic, readonly, getter = isDownloading) BOOL downloading;
20
21 /**
22 * 用來監聽下載下傳進度
23 */
24 @property (nonatomic, copy) void (^progressHandler)(double progress);
25
26 /**
27 * 開始(恢複)下載下傳
28 */
29 - (void)start;
30
31 /**
32 * 暫停下載下傳
33 */
34 - (void)pause;
35 @end
YYFileDownloader.m檔案
1 #import "YYFileDownloader.h"
2
3 @implementation YYFileDownloader
4 @end
下載下傳器類繼承自YYFileDownloader這個類
YYFileSingDownloader.h檔案
1 #import "YYFileDownloader.h"
2
3 @interface YYFileSingleDownloader : YYFileDownloader
4 /**
5 * 開始的位置
6 */
7 @property (nonatomic, assign) long long begin;
8 /**
9 * 結束的位置
10 */
11 @property (nonatomic, assign) long long end;
12 @end
YYFileSingDownloader.m檔案
1 #import "YYFileSingleDownloader.h"
2 @interface YYFileSingleDownloader() <NSURLConnectionDataDelegate>
3 /**
4 * 連接配接對象
5 */
6 @property (nonatomic, strong) NSURLConnection *conn;
7
8 /**
9 * 寫資料的檔案句柄
10 */
11 @property (nonatomic, strong) NSFileHandle *writeHandle;
12 /**
13 * 目前已下載下傳資料的長度
14 */
15 @property (nonatomic, assign) long long currentLength;
16 @end
17
18 @implementation YYFileSingleDownloader
19
20 - (NSFileHandle *)writeHandle
21 {
22 if (!_writeHandle) {
23 _writeHandle = [NSFileHandle fileHandleForWritingAtPath:self.destPath];
24 }
25 return _writeHandle;
26 }
27
28 /**
29 * 開始(恢複)下載下傳
30 */
31 - (void)start
32 {
33 NSURL *url = [NSURL URLWithString:self.url];
34 // 預設就是GET請求
35 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
36 // 設定請求頭資訊
37 NSString *value = [NSString stringWithFormat:@"bytes=%lld-%lld", self.begin + self.currentLength, self.end];
38 [request setValue:value forHTTPHeaderField:@"Range"];
39 self.conn = [NSURLConnection connectionWithRequest:request delegate:self];
40
41 _downloading = YES;
42 }
43
44 /**
45 * 暫停下載下傳
46 */
47 - (void)pause
48 {
49 [self.conn cancel];
50 self.conn = nil;
51
52 _downloading = NO;
53 }
54
55
56 #pragma mark - NSURLConnectionDataDelegate 代理方法
57 /**
58 * 1. 當接受到伺服器的響應(連通了伺服器)就會調用
59 */
60 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
61 {
62
63 }
64
65 /**
66 * 2. 當接受到伺服器的資料就會調用(可能會被調用多次, 每次調用隻會傳遞部分資料)
67 */
68 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
69 {
70 // 移動到檔案的尾部
71 [self.writeHandle seekToFileOffset:self.begin + self.currentLength];
72 // 從目前移動的位置(檔案尾部)開始寫入資料
73 [self.writeHandle writeData:data];
74
75 // 累加長度
76 self.currentLength += data.length;
77
78 // 列印下載下傳進度
79 double progress = (double)self.currentLength / (self.end - self.begin);
80 if (self.progressHandler) {
81 self.progressHandler(progress);
82 }
83 }
84
85 /**
86 * 3. 當伺服器的資料接受完畢後就會調用
87 */
88 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
89 {
90 // 清空屬性值
91 self.currentLength = 0;
92
93 // 關閉連接配接(不再輸入資料到檔案中)
94 [self.writeHandle closeFile];
95 self.writeHandle = nil;
96 }
97
98 /**
99 * 請求錯誤(失敗)的時候調用(請求逾時\斷網\沒有網, 一般指用戶端錯誤)
100 */
101 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
102 {
103
104 }
105
106 @end
設計多線程下載下傳器(利用HMFileMultiDownloader能開啟多個線程同時下載下傳一個檔案)
一個多線程下載下傳器隻下載下傳一個檔案
YYFileMultiDownloader.h檔案
1 #import "YYFileDownloader.h"
2
3 @interface YYFileMultiDownloader : YYFileDownloader
4
5 @end
YYFileMultiDownloader.m檔案
1 #import "YYFileMultiDownloader.h"
2 #import "YYFileSingleDownloader.h"
3
4 #define YYMaxDownloadCount 4
5
6 @interface YYFileMultiDownloader()
7 @property (nonatomic, strong) NSMutableArray *singleDownloaders;
8 @property (nonatomic, assign) long long totalLength;
9 @end
10
11 @implementation YYFileMultiDownloader
12
13 - (void)getFilesize
14 {
15 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
16 request.HTTPMethod = @"HEAD";
17
18 NSURLResponse *response = nil;
19 #warning 這裡要用異步請求
20 [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
21 self.totalLength = response.expectedContentLength;
22 }
23
24 - (NSMutableArray *)singleDownloaders
25 {
26 if (!_singleDownloaders) {
27 _singleDownloaders = [NSMutableArray array];
28
29 // 獲得檔案大小
30 [self getFilesize];
31
32 // 每條路徑的下載下傳量
33 long long size = 0;
34 if (self.totalLength % YYMaxDownloadCount == 0) {
35 size = self.totalLength / YYMaxDownloadCount;
36 } else {
37 size = self.totalLength / YYMaxDownloadCount + 1;
38 }
39
40 // 建立N個下載下傳器
41 for (int i = 0; i<YYMaxDownloadCount; i++) {
42 YYFileSingleDownloader *singleDownloader = [[YYFileSingleDownloader alloc] init];
43 singleDownloader.url = self.url;
44 singleDownloader.destPath = self.destPath;
45 singleDownloader.begin = i * size;
46 singleDownloader.end = singleDownloader.begin + size - 1;
47 singleDownloader.progressHandler = ^(double progress){
48 NSLog(@"%d --- %f", i, progress);
49 };
50 [_singleDownloaders addObject:singleDownloader];
51 }
52
53 // 建立一個跟伺服器檔案等大小的臨時檔案
54 [[NSFileManager defaultManager] createFileAtPath:self.destPath contents:nil attributes:nil];
55
56 // 讓self.destPath檔案的長度是self.totalLengt
57 NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:self.destPath];
58 [handle truncateFileAtOffset:self.totalLength];
59 }
60 return _singleDownloaders;
61 }
62
63 /**
64 * 開始(恢複)下載下傳
65 */
66 - (void)start
67 {
68 [self.singleDownloaders makeObjectsPerformSelector:@selector(start)];
69
70 _downloading = YES;
71 }
72
73 /**
74 * 暫停下載下傳
75 */
76 - (void)pause
77 {
78 [self.singleDownloaders makeObjectsPerformSelector:@selector(pause)];
79 _downloading = NO;
80 }
81
82 @end
補充說明:如何獲得将要下載下傳的檔案的大小?