美文网首页断点续传iOS学习iOS Developer
iOS开发下载、断点续传-NSURLConnection、NSU

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

作者: 伍骁辛 | 来源:发表于2016-09-01 10:24 被阅读731次

    最近在研究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下载:
    https://github.com/maying1992/NSURLSession.git

    相关文章

      网友评论

      • zhengelababy:什么?写的这是断点下载就断电下载,不是断点续传好吗
      • yurnery:楼主,问一下,NSURLConnection 使用代理进行下载的时候,为什么在代理方法里监听到的线程都是主线程,难道不应该是多线程下载吗
        伍骁辛:@yurnery 可以实现断点下载,设置HTTP协议中请求头Range
        yurnery:@伍骁辛 NSURlConnection是不是不能实现离线断点下载啊
        伍骁辛:@yurnery 进行大文件下载的时候会阻塞主线程,这个时候可以使用setDelegateQueue设置代理方法和下载任务的线程,也就是可以在子线程上创建NSURLConnection
      • 张云龙:目前下载的难点不是断点续传,这个在NSURLSession发布后可以很简单的实现。难点1、后台系在,进入前台继续下载;2、突然断网后如何保存resumeData,直接调用
        [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        // resumeData : 包含了继续下载的开始位置\下载的url
        weakSelf.resumeData = resumeData;
        weakSelf.downloadTask = nil;
        }];
        是获取不到resumeData数据的,会得到空。
        3、程序终止,再重启后如何断点续传。
        这3个难点解决了才可以,博主目前的demo只能在非常理想的情况下实现断点续传,是否有思路解决上面3点难题,求指教,最近也在自己封装。
        伍骁辛:@张云龙 首先切换后台应用挂起UIApplicationDelegate委托会收到通知,我们可以重写挂起前这个方法,做一些工作,比如保存数据。当程序复原时,becomActive会被调用,这时候可以通过挂起前保存的数据恢复应用程序。第二可以通过runtime接口找到内部属性,最终找到下载时候临时文件的地址。
      • macfai:楼主,你那个demo 是个空的啊,你是不是上传错了,有空的话看一下吧
        伍骁辛:@macfai 好的 我晚上重新传下
      • macfai:楼主讲的不错,下载来学习下,不过你用这个或afn写过图片批量上传的demo 吗, :smile:
        伍骁辛:@macfai AFN也是内部也是NSURLSession实现的,都差不多

      本文标题:iOS开发下载、断点续传-NSURLConnection、NSU

      本文链接:https://www.haomeiwen.com/subject/utmqettx.html