本文讨论单线程的文件下载和断点续传,经过从本地服务器下载一个较大的文件,实现显示进度、中途暂停与断点续传。服务器
下载过程大体以下:网络
①经过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