本文討論單線程的文件下載和斷點續傳,經過從本地服務器下載一個較大的文件,實現顯示進度、中途暫停與斷點續傳。服務器
下載過程大體以下:網絡
①經過NSURL建立指向特定下載地址的請求,本文中下載的文件位於網站根目錄的lesson1下的nav.dmg,所以URL應爲http://127.0.0.1/lesson1/nav.dmg。less
②經過NSURL建立URLRequest,爲了可以更改HTTP請求頭,實現特定字節的下載,使用NSMutableURLRequest,而後設置請求頭的Range範圍,range的主要寫法是bytes=x-y,表明下載x-y的字節,注意字節的起始是0。也能夠忽略一端,例如x-表明下載x和之後的字節;而-y表示下載最後y個字節。網站
③使用NSURLConnection執行請求,而且設置代理,經過代理方法接收數據。atom
爲了可以實時計算進度,咱們使用一系列變量,記錄下載了的字節數和總字節數:url
@property (nonatomic, assign) long long totalLength; @property (nonatomic, assign) long long currentLength;
①- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)errorspa
當出錯時來到這裏。線程
②- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response代理
這個比較重要,在下載文件以前會先拿到這個響應頭,從中能夠拿到文件的大小,在這裏適合作一些初始化工做,例如0B文件的建立和總大小、當前大小的初始化。code
③- (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