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 这个方法,这样在下载过程中内存的问题就解决了。

屏幕快照 2016-08-31 上午10.55.49.png

断点下载

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

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/maying/Library/Developer/CoreSimulator/Devices/42CE6C49-4CC6-47A3-8992-B8CABE1A9678/data/Containers/Data/Application/1A6948E7-78AC-478D-9751-E25AC199B359

屏幕快照 2016-08-31 下午1.57.11.png

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


屏幕快照 2016-08-31 下午2.01.34.png

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

创建任务如下
// 得到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下载:
本文Demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容