1, NSURLConnection实现断点续传
首先明确几个关键点:
1, cancel方法.
首先NSURLConnection有一个cancel方法,可以取消正在进行的下载操作,但是取消后无法再恢复当前下载链接.(这个和操作队列的挂起是本质区别的). 当重现建立下载链接的时候,又是从头开始下载了.
2, http请求头,range字段.
通过设定renge头,我们可以限定下载资源的哪一部分.
实例代码如下:
NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentFileSize];
[request setValue:range forHTTPHeaderField:@"Range"];
3, 思路:
其实基于以上两点,我们的思路就已经有了:
当我们再次发起请求下载资源的时候:
1,首先我们应该检查一下本地是否已近有这个文件了,文件的长度是多少等等.
2,然后在通过发送"HEAD" 请求,来获得服务端资源的一些信息:文件长度,类型等等
3,基于以上两步操作,对比本地文件长度和服务端文件长度,这样就知道资源的下载进度了,从而计算出 range 头.
4,再次发送带 "range头"的请求.继续下载文件.实现断点续传.
直接上代码:
#import "ViewController.h"
#import <NewsstandKit/NewsstandKit.h>
@interface ViewController ()<NSURLConnectionDataDelegate>
// 保存下载文的件路径
@property(nonatomic,copy) NSString *destinationPath;
// 文件总大小
@property(nonatomic,assign) long long expectedContentLenght;
// 当前接收文件大小
@property(nonatomic,assign) long long currentFileSize;
// 文件输出流
@property(nonatomic,strong) NSOutputStream *fileStream;
// 请求链接对象
@property(nonatomic,strong) NSURLConnection *connection;
@end
@implementation ViewController
/**
* 暂停下载
*/
- (IBAction)pauseDownLoad {
[self.connection cancel];
}
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// url 字符串
NSString *urlStr = @"http://localhost/图片浏览器.mp4";
// 添加百分号转义
urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// 请求路径
NSURL *url = [NSURL URLWithString:urlStr];
// 检查服务器文件信息
[self checkServerFileInfo:url];
// 检查本地文件信息
self.currentFileSize =[self checkLocalFileInfo];
// 文件大小相等
if (self.currentFileSize == self.expectedContentLenght) {
NSLog(@"下载完成");
return;
}
// 断点续传---一定不能使用缓存数据
// 请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0];
// 创建 range 头
NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentFileSize];
[request setValue:range forHTTPHeaderField:@"Range"];
// 建立连接,立即启动
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
// 启动 runLoop
[[NSRunLoop currentRunLoop] run];
});
}
// 检查服务器的文件信息
- (void)checkServerFileInfo:(NSURL *)url{
// 创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 设置请求方法
request.HTTPMethod = @"HEAD";
NSURLResponse *response = nil;
// 发送同步请求(这里必须要用同步)
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
// 得到服务器响应
// 1> 目标文件大小
self.expectedContentLenght = response.expectedContentLength;
// 2> 保存文件路径
self.destinationPath = [NSTemporaryDirectory() stringByAppendingPathComponent:response.suggestedFilename];
}
// 检查本地文件的信息
- (long long)checkLocalFileInfo{
// 获得文件管理对象
NSFileManager *fileManager = [NSFileManager defaultManager];
// 记录本地文件的大小
long long fileSize = 0;
// 判断文件是否存在
if([fileManager fileExistsAtPath:self.destinationPath]) {
// 文件存在,则获得文件信息
NSDictionary *attr = [fileManager attributesOfItemAtPath:self.destinationPath error:NULL];
// 直接从字典中获得文件大小
fileSize = attr.fileSize;
}
// 如果大于服务器文件大小,直接删除
if(fileSize > self.expectedContentLenght) {
[fileManager removeItemAtPath:self.destinationPath error:NULL];
fileSize = 0;
}
return fileSize;
}
#pragma mark - NSURLConnectionDataDelegate 代理方法
/**
* 接收到服务器响应的时候调用(状态行和响应头)
*/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// NSLog(@"response = %@",response);
// 根据文件名 创建输出流对象
self.fileStream = [NSOutputStream outputStreamToFileAtPath:self.destinationPath append:YES];
// 打开流
[self.fileStream open];
}
/**
* 接收到服务器返回的数据时调用,可能会被调用多次(所有的 data 的数据都是按顺序传递过来的)
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
self.currentFileSize += data.length;
// 计算进度值
CGFloat progress = (CGFloat)self.currentFileSize / self.expectedContentLenght;
NSLog(@"接收到数据 = %f",progress);
// 拼接数据
[self.fileStream write:data.bytes maxLength:data.length];
}
/**
* 网络请求结束调用(断开网络)
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(@"下载完成");
// 关闭流
[self.fileStream close];
}
/**
* 网络连接发生错误的时候调用(任何网络请求都有可能出现错误)
* 在实际开发中一定要进行出错处理
*/
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"error = %@",error);
// 关闭流
[self.fileStream close];
}
@end