iOS开发下载、断点续传-NSURLConnection、NSURLSession

最近在研究NSULRSession,顺道总结了NSURLConnection与NSULRSession区别与联系,仅供交流学习,欢迎各位大神指正。

NSURLConnection

NSURLConnection指的是一组构成Foundation框架中URL加载系统的相互关联的组件:NSURLRequest,NSURLResponse,NSURLProtocol,NSURLCache。

创建connection
// 1.URL
NSURL* url = [NSURL URLWithString:@"http://dlsw.baidu.com/sw-search-sp/soft/9d/25765/sogou_mac_32c_V3.2.0.1437101586.dmg"];

//2.请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

//设置请求头
NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentLength];
[request setValue:range forHTTPHeaderField:@"Range"];

//3.下载
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];

这里用到了代理,遵守NSURLConnectionDataDelegate 协议.
下面是代理方法

// 请求失败时调用(请求超时、网络异常)
-(void)connection:(NSURLConnectio**)connection didFailWithError:(NSError *)error{
}

// 1.接收到服务器的响应就会调用
-(void)connection:(NSURLConnection**)connection didReceiveResponse:(NSURLResponse *)response{
}

// 2.当接收到服务器返回的实体数据时调用(具体内容,这个方法可能会被调用多次)
-(void)connection:(NSURLConnection**)connection didReceiveData:(NSData *)data{
}

// 3.加载完毕后调用(服务器的数据已经完全返回后)
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
}

通过didReceiveData这个代理方法每次传回来一部分文件,最终我们把每次传回来的数据拼接合并成一个我们需要的文件写入沙盒,最终就获取到了我们需要的数据,需要注意的在我们获取一部分data的时候就写入沙盒中,然后释放内存中的data,而不是直接用来一个接受文件的NSMutableData,它一直都在内存中,会随着文件的下载一直变大。

写入的时候这里要用到NSFilehandle这个类,这个类可以实现对文件的读取、写入、更新。在接受到响应的时候就在沙盒中创建一个空的文件,然后每次接收到数据的时候就拼接到这个文件的最后面,通过- (unsigned long long)seekToEndOfFile 这个方法,这样在下载过程中内存的问题就解决了。

这里写图片描述

断点下载

暂停/继续下载是我们下载中过程中必不可少的的功能了,如果没有暂停功能,用户体验相比会很差,而且实际场景下如果突然网络不好中断了,没有实现断点下载的话我们只能重新下载了,用户体验非常不好。
下面我们来了解断点下载功能。

NSURLConnection 只提供了一个cancel方法,这并不是暂停,而是取消下载任务。如果要实现断点下载必须要了解HTTP协议中请求头的Range,通过设置请求头的Range我们可以指定下载的位置、大小。
如果我们这样设置bytes=500-,表示从500字节以后的所有字节,只需要在didReceiveData中记录已经写入沙盒中文件的大小,把这个大小设置到请求头中,因为第一次下载肯定是没有执行过didReceive方法,self.currentLength也就为0,也就是从头开始下。

代码如下:

-(void)ButtonAction:(UIButton *)sender
{

sender.selected = !sender.selected;

if (sender.selected) {

// 1.URL
NSURL* url = [NSURL URLWithString:@"http://dlsw.baidu.com/sw-search-sp/soft/9d/25765/sogou_mac_32c_V3.2.0.1437101586.dmg"];

//2.请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//设置请求头
NSString *range = [NSString stringWithFormat:@"bytes=%lld-",self.currentLength];
[request setValue:range forHTTPHeaderField:@"Range"];

//3.下载
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];

} else {
[self.connection cancel];
self.connection = nil;
}
}

ps:为了提高下载的效率,我们一般采用多线程下载。

NSURLSession

NSURLSession是iOS7之后新的网络接口,NSURLSession也是一组相互依赖的类,而NSURLSession的不同之处在于,它将NSURLConnection替换为 NSURLSession和 NSURLSessionConfiguration,以及3个 NSURLSessionTask
的子类: NSURLSessionDataTask , NSURLSessionUploadTask, 和NSURLSessionDownloadTask。另外,上面的NSURLConnection要自己去控制内存写入相应的位置,而NSURLSession则不需要手动写入沙盒,更加方便了我们的使用。

三种任务类型:
1.NSURLSessionDataTask : 普通的GET\POST请求
2.NSURLSessionDownloadTask : 文件下载3.NSURLSessionUploadTask : 文件上传(很少用,一般服务器不支持)

NSURLSession 使用

NSURLSession请求
// 1.得到session对象
NSURLSession* session = [NSURLSession sharedSession];
NSURL* url = [NSURL URLWithString:@""];

// 2.创建一个task,任务
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

//data返回数据
}];

//    [session dataTaskWithRequest:<#(NSURLRequest *)#> completionHandler:<#^(NSData *data, NSURLResponse *response, NSError *error)completionHandler#>]

//3.开始任务
[dataTask resume];

NSURLSession 下载

使用NSURLSession下载相对于NSURLConnection就非常简单了,不需要去手动控制边下载边写入沙盒的问题,苹果都帮我们做好了。

代码如下:

NSURL* url = [NSURL URLWithString:@"http://dlsw.baidu.com/sw-search-sp/soft/9d/25765/sogou_mac_32c_V3.2.0.1437101586.dmg"];

// 得到session对象
NSURLSession *session = [NSURLSession sharedSession];

//创建任务
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {

// location : 临时文件的路径(下载好的文件),也就是下载好的文件写入沙盒的地址,打印一下发现下载好的文件被自动写入的temp文件夹下面了。

NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// response.suggestedFilename : 建议使用的文件名,一般跟服务器端的文件名一致
NSString *file = [caches stringByAppendingPathComponent:response.suggestedFilename];

// 将临时文件剪切或者复制Caches文件夹
NSFileManager *mgr = [NSFileManager defaultManager];

// AtPath : 剪切前的文件路径
// ToPath : 剪切后的文件路径
[mgr moveItemAtPath:location.path toPath:file error:nil];
}];

// 开始任务
[downloadTask resume];

sandbox:/Users/reborn/Library/Developer/CoreSimulator/Devices/42CE6C49-4CC6-47A3-8992-B8CABE1A9678/data/Containers/Data/Application/1A6948E7-78AC-478D-9751-E25AC199B359

这里写图片描述

但是在下载完成之后会自动删除temp中的文件,所有我们需要做的只是在回调中把文件移动(或者复制,反正之后会自动删除)到caches中,也就是上面将临时文件剪切或者复制Caches文件夹的过程。

下载完结果如下:
这里写图片描述

ps:通过这种方式下载有个缺点就是无法监听下载进度,要监听下载进度,我们通常的作法是通过delegate,而且NSURLSession的创建方式也有所不同。首先遵守协议
协议里面有三个方法。

创建任务如下
// 得到session对象
NSURLSessionConfiguration *configuration =     [NSURLSessionConfiguration defaultSessionConfiguration] ;                                    

//默认配置
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
//创建任务 
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url]; 

//开始任务
[downloadTask resume];

协议方法如下

#pragma mark -- NSURLSessionDownloadDelegate
//1.下载完毕会调用 (@param location,文件临时地址)
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];

// response.suggestedFilename:建议使用的文件名,一般跟服务器端的文件名一致
NSString *filePath = [cache stringByAppendingPathComponent:downloadTask.response.suggestedFilename];

//将临时文件剪切或复制到Caches文件夹
NSFileManager *fileManager = [NSFileManager defaultManager];

// AtPath : 剪切前的文件路径 ,ToPath : 剪切后的文件路径
[fileManager moveItemAtPath:location.path toPath:filePath error:nil];

NSLog(@"下载完成");
}
//2.执行下载任务时有数据写入,在这里面监听下载进度   (totalBytesWritten/totalBytesExpectedToWrite
 @param bytesWritten              这次写入的大小
 @param totalBytesWritten         已经写入的大小
 @param totalBytesExpectedToWrite 文件总大小)

-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{

self.myProgress.progress = (double)totalBytesWritten/totalBytesExpectedToWrite;

self.progressDesLabel.text = [NSString stringWithFormat:@"下载进度%f:",(double)totalBytesWritten/totalBytesExpectedToWrite];
}

//3.恢复下载后调用
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{

}
NSURLSessionDownloadTask断点下载

取消任务

 __weak typeof(self) weakSelf = self;

 [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
//  resumeData : 包含了继续下载的开始位置\下载的url
weakSelf.resumeData = resumeData;
weakSelf.downloadTask = nil;
}];

ps:需要注意的是Block中循环引用的问题

取消操作调用一个Block回调后传入一个resumeData,该参数包含了继续下载文件的位置信息。也就是说,当我们下载了200M的文件数据,突然暂停了。下次当我们进来的时候继续下载的是从第200M这个位置开始的,而不是从文件最开始的位置开始下载。因而为了保存这些信息,所以才定义了resumeData这个NSData类型的属性,这个data包含了url和继续下载的位置,也就是已经下载数据的大小。

通过resumeData来创建任务的方法

-(NSURLSessionDownloadTask**)downloadTaskWithResumeData:(NSData*)resumeData;

因此,我们要做的就是在取消操作的回调中记录好resumeData,然后在恢复下载的时候调用上面的方法创建任务就好了,相对NSURLconnection手动写入沙盒方便了不少。需要注意的是下载比较耗费资源,我们可以采用多线程分条下载后组成我们需要的文件数据。

本文示例demo下载

阅读更多
换一批

没有更多推荐了,返回首页