(一二六)單線程檔案下載與斷點續傳,一二六單線程
本文討論單線程的檔案下載和斷點續傳,通過從本機伺服器下載一個較大的檔案,實現顯示進度、中途暫停與斷點續傳。
下載過程大致如下:
①通過NSURL建立指向特定的請求,本文中下載的檔案位於網站根目錄的lesson1下的nav.dmg,因此URL應為http://127.0.0.1/lesson1/nav.dmg。
②通過NSURL建立URLRequest,為了能夠更改HTTP要求標頭,實現特定位元組的下載,使用NSMutableURLRequest,然後佈建要求頭的Range範圍,range的主要寫法是bytes=x-y,代表下載x-y的位元組,注意位元組的起始是0。也可以忽略一端,例如x-代表下載x和以後的位元組;而-y表示下載最後y個位元組。
③使用NSURLConnection執行請求,並且設定代理,通過代理方法接收資料。
為了能夠Realtime Compute進度,我們使用一系列變數,記錄下載了的位元組數和總位元組數:
@property (nonatomic, assign) long long totalLength;@property (nonatomic, assign) long long currentLength;
【NSURLConnection的代理方法】
①- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
當出錯時來到這裡。
②- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
這個比較重要,在下載檔案之前會先拿到這個回應標頭,從中可以拿到檔案的大小,在這裡適合做一些初始化工作,例如0B檔案的建立和總大小、當前大小的初始化。
③- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
每當檔案下載完一小塊,就會調用一次,從這裡可以進行檔案拼接,進度更新。
④- (void)connectionDidFinishLoading:(NSURLConnection *)connection
當檔案下載完成時調用這個方法,一般在這裡關閉檔案。
很顯然網路暢通時的調用順序是②->多次③->④。
【檔案的操作】
①通過NSFileManager建立空檔案,用於資料拼接。
// 先建立一個空檔案NSFileManager *mgr = [NSFileManager defaultManager];NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"download.dmg"];[mgr createFileAtPath:path contents:nil attributes:nil];
②為了能夠方便拼接,使用NSFileHandle指向檔案,並且儲存成成員變數。
@property (nonatomic, strong) NSFileHandle *handle;
// 使用檔案控制代碼操作檔案NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:path];_handle = handle;_totalLength = response.expectedContentLength;_currentLength = 0;
③檔案的拼接
通過NSFileHandle的seekToEndOfFile方法可以指向當前檔案尾,然後使用writeData方法向後拼接。
// 下載拼接思路:先建立空檔案,然後一部分一部分的拼接[self.handle seekToEndOfFile];[self.handle writeData:data];
總體來說,思路是一部分一部分的拼出最終檔案。
【斷點續傳】
暫停只能通過結束NSURLConnection實現,connection一旦結束就會失效,只能重新建立。
[self.connection cancel];self.connection = nil;
為了實現下次再從中斷的位置下載,只需要通過_currentLength即可,讓Range為bytes:_currentLength-即可,因為位元組是從0開始的,當前長度為len代表下一個要下載的位元組編號就是len。
請求代碼如下:
NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/lesson1/nav.dmg"];// 通過佈建要求頭range來設定下載資料的範圍NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentLength];[request setValue:range forHTTPHeaderField:@"Range"];self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
另一個注意點是因為新建立了請求,會再去擷取回應標頭,這樣又會用空檔案覆蓋原檔案,重新下載,我們加一個判斷,如果_currentLength≠0,直接返回即可。
下面是完整的代碼,其中downloadBar是進度條,btn用於控制下載開始與暫停,二者均來自storyboard。btn點擊後延時0.5秒開始下載是為了防止點擊後按鈕被阻塞無法到達暫停鍵狀態。
//// ViewController.m// 大檔案下載//// Copyright (c) 2015 soulghost. All rights reserved.//#import "ViewController.h"@interface ViewController () <NSURLConnectionDataDelegate>@property (nonatomic, strong) NSFileHandle *handle;@property (nonatomic, assign) long long totalLength;@property (nonatomic, assign) long long currentLength;@property (weak, nonatomic) IBOutlet UIProgressView *downloadBar;@property (weak, nonatomic) IBOutlet UIButton *btn;@property (nonatomic, strong) NSURLConnection *connection;@end@implementation ViewController- (void)startDownload{ NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/lesson1/nav.dmg"]; // 通過佈建要求頭range來設定下載資料的範圍 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentLength]; // 從0開始,下到10應該是0-9.下面應該下載10. [request setValue:range forHTTPHeaderField:@"Range"]; self.connection = [NSURLConnection connectionWithRequest:request delegate:self]; }- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ }- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{ NSLog(@"下載出錯"); }- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{ if (_currentLength) { return; } // 先建立一個空檔案 NSFileManager *mgr = [NSFileManager defaultManager]; NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"download.dmg"]; [mgr createFileAtPath:path contents:nil attributes:nil]; // 使用檔案控制代碼操作檔案 NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:path]; _handle = handle; _totalLength = response.expectedContentLength; _currentLength = 0; self.downloadBar.progress = 0; }- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{ // 下載拼接思路:先建立空檔案,然後一部分一部分的拼接 [self.handle seekToEndOfFile]; [self.handle writeData:data]; self.currentLength += data.length; self.downloadBar.progress = (float)self.currentLength / self.totalLength; NSLog(@"進度:%f",self.downloadBar.progress);; }- (void)connectionDidFinishLoading:(NSURLConnection *)connection{ [self.handle closeFile]; [self.btn setTitle:@"開始" forState:UIControlStateNormal]; self.currentLength = 0; }- (IBAction)btnClick:(UIButton *)sender { if ([sender.titleLabel.text isEqualToString:@"開始"]) { [sender setTitle:@"暫停" forState:UIControlStateNormal]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self startDownload]; }); }else{ [sender setTitle:@"開始" forState:UIControlStateNormal]; [self.connection cancel]; self.connection = nil; } }@end
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。