NSURLSession-介绍、上传、下载(2)

NSURLConnection在iOS9被宣布弃用,NSURLSession从13年发展到现在,终于迎来了它独步江湖的时代.NSURLSession是苹果在iOS7后为HTTP数据传输提供的一系列接口,比NSURLConnection强大,坑少。
一、NSURLSession的简介
1.NSURLSession的创建
(1)使用shareSession返回session的单例,创建会话对象
NSURLSession *session = [NSURLSession sharedSession];
(2)使用NSURLSessionConfiguration来配置session,NSURLSessionConfiguration对象用于初始化NSURLSession对象,展开请求级别中与NSMutableURLRequet相关的方案NSURLSessionConfiguration对于会话如何产生请求,提供了相当多的控制和灵活性。从网络访问性能,到cookie,安全性,缓存策略,自定义协议,启动事件设置,以及用于移动设备优化的几个新属性,你会发现你一直在寻找的,正是NSURLSessionConfiguration。配置在初始化时被读取一次,之后都是不会变化的。

       + defaultSessionConfiguration返回标准配置,这实际上与NSURLConnection的网络协议栈是一样的,具有相同的共享NSHTTPCookieStorage,共享NSURLCache和共享NSURLCredentialStorage。
      + ephemeralSessionConfiguration返回一个预设配置,没有持久性存储的缓存,Cookie或证书。这对于实现像秘密浏览功能的功能来说,是很理想的。
       + backgroundSessionConfiguration:独特之处在于,它会创建一个后台会话。后台会话不同于常规的,普通的会话,它甚至可以在应用程序挂起,退出,崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程提供上下文。

创建和配NSURLSession的示例代码如下:

//默认类型的
    NSURLSessionConfiguration * defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    //即时类型的
    NSURLSessionConfiguration * ephemeralConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
    //后台类型的
    NSURLSessionConfiguration * backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"SessionId"];

    //创建并设置session
    NSURLSession * defaultSession = [NSURLSession sessionWithConfiguration:defaultConfiguration];
    NSURLSession * ephemeralSession = [NSURLSession sessionWithConfiguration:ephemeralConfiguration];
    NSURLSession * backgroundSession = [NSURLSession sessionWithConfiguration:backgroundConfiguration];

(3)NSURLSession的优势
*它支持http2.0
*支持后台上传/下载,下载时是多线程异步处理,处理任务时直接把数据下载到磁盘上
*提供全局的session并且可以统一配置
*同一个session发送多个请求,只需建立一次连接(复用TCP)

2、NSURLSessionTask
NSURLSessionTask是一个抽象类,使用时应该使用它的子类;NSURLSessionTask有两个子类,
(1)NSURLSessionDataTask,可以用来处理一般的网络请求,如GET、POST,它的子类NSURLSessionUploadTask多用来处理上传请求。
(2)NSURLSessionDownloadTask,主要用来处理下载请求。
3、下载任务
(1)NSURLConnection文件下载
对于小文件的下载,直接使用sendAsynchronousRequest:queue:completionHandler:发送一个异步的get请求,回调的data就是下载的内容,它放在内存中;这种下载方式简单,是一次性将下载内容下载完,适用于小文件的下载。如果下载大文件,一次性下载完,内存会爆。
NSURLConnection下载大文件主要是使用它的代理方法,其中didReceiveData代理方法用来接受数据,他会被频繁调用,每次传回来一部分data,我们只需定义一个全局的NSMutableData(在didReceiveResponse接受到响应时初始化),在didReceiveData中拼接,最后在connectionDidFinishLoading中将整个NSMutableData写入到沙盒中。
注意:通常大文件下载是需要给用户展示下载进度的。
这个数值是: 已经下载的数据大小/要下载的文件总大小。已经下载的数据我们可以记录,要下载的文件总大小在服务器返回的响应头里面可以拿到,在接受到响应的方法里执行

NSHTTPURLResponse *res = (NSHTTPURLResponse*)response;

    NSDictionary *headerDic = res.allHeaderFields;
    NSLog(@"%@",headerDic);
    self.fileLength = [[headerDic objectForKey:@"Content-Length"] intValue];

不得不说苹果太为开发者考虑了,我们不必这么麻烦的去获取文件总大小了,
response.expectedContentLength 这句代码就搞定了。
response.suggestedFilename 这句代表获取下载的文件名

但是,上述的处理方法会出现内存问题,应为用来接受文件的NSmutableData一直在内存中,所以随着文件的下载内存会一直变大,解决的方法是获取到一部分data时就写入到沙盒,然后释放内存中的data

断点下载

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

#pragma mark --按钮点击事件

- (IBAction)btnClicked:(UIButton *)sender {

    // 状态取反
    sender.selected = !sender.isSelected;

    // 断点续传
    // 断点下载

    if (sender.selected) { // 继续(开始)下载
        // 1.URL
        NSURL *url = [NSURL URLWithString:@"http://localhost:8080//term_app/hdgg.zip"];

        // 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;
    }

在下载过程中,为了提高效率,充分利用cpu性能,通常会执行多线程下载,代码就不贴了,分析一下思路:
下载开始,创建一个和要下载的文件大小相同的文件(如果要下载的文件为100M,那么就在沙盒中创建一个100M的文件,然后计算每一段的下载量,开启多条线程下载各段的数据,分别写入对应的文件部分)。

(2)NSURLSession下载
使用NSURLSessionDownload不需要考虑边下载,边写入沙盒的问题了,因为苹果为我们做好了,调用downloadTaskWithURL:completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error)即可。

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 *location, NSURLResponse *response, NSError *error) {

    }];
    // 开始任务
    [downloadTask resume];

回调中的location是下载好的文件写入到沙盒temp中的地址,因为temp中的文件会自动删除,所以我们要在回调中把文件移到cache中。

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];

缺点:不过通过这种方式下载有个缺点就是无法监听下载进度,要监听下载进度,苹果通常的作法是通过delegate,要遵
- (void)URLSession:(NSURLSession )session downloadTask:(NSURLSessionDownloadTask )downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite方法可以监听下载进度

#pragma mark -- NSURLSessionDownloadDelegate
/**
 *  下载完毕会调用
 *
 *  @param location     文件临时地址
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
}
/**
 *  每次写入沙盒完毕调用
 *  在这里面监听下载进度,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.pgLabel.text = [NSString stringWithFormat:@"下载进度:%f",(double)totalBytesWritten/totalBytesExpectedToWrite];
}

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

}

断点续传
用NSURLSessionDownloadTask做断点下载也很简单,我们先了解一下任务的取消方法
断点续传—
断点续传涉及到的类和方法
  NSURLSessionDownloadTask:
  - (void) suspend; 暂停 ,可以恢复
  - (void) cancel; 取消,不可以恢复
  - (void) cancelByProducingResumeData:^(NSData * _Nullable resumeData) : ; 取消的任务
  - (void) resume; 在创建新的任务下resume,相当于重新启动任务

注意:(1)如果使用suspend方法暂停下载,因为是可恢复的,那么对应的下载任务对象是唯一的。使用的时候suspend要和resume成对使用,都是同一个NSURLSessionDownloadTask调用的对象方法。
    (2)如果使用cancel,就相当于同时将NSURLSessionDwonloadTask任务也被取消了。所以如果要重新下载就需要重新创建NSURLSessionDownloadTask对象,而且,下载的内容不是重头开始
(3)如果使用cancel是无法恢复下载,但是为了能够恢复下载就只能用 cancelByProducingResumeData:^(NSData * _Nullable resumeData)方法,其中这个方法中的resumeData存储的是之前已经下载好的数据相关的信息:文件名,存储位置,已经下载好的数据的长度等信息,并不是下载的数据本身。恢复下载也是需要通过这个resumeData来恢复,然后继续下载。同时也要重新创建下载任务对象NSURLSessionDownloadTask。

 - (void)cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler;

取消操作以后会调用一个Block,并传入一个resumeData,该参数包含了继续下载文件的位置信息。也就是说,当你下载了10M得文件数据,暂停了。那么你下次继续下载的时候是从第10M这个位置开始的,而不是从文件最开始的位置开始下载。因而为了保存这些信息,所以才定义了这个NSData类型的这个属性:resumeData。这个data只包含了url跟已经下载了多少数据,不会很大,不用担心内存问题。另外,session还提供了通过resumeData来创建任务的方法

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

我们只需要在取消操作的回调中记录好resumeData,然后在恢复下载的适合通过上面的方法创建任务就好了,相比NSURLconnection简单太多了。

关于文件下载与暂停的分析
当使用NSURLSessionDownloadTask进行下载的时候,系统会在cache文件夹下创建一个下载的路径,路径下会有一个以”CFNetworking”打头的.tmp文件(以下简称”下载文件”防止混淆),这个就是我们正在下载中的文件。而当我们调用了cancelByProducingResumeData:方法后,会得到一个data文件,通过String格式化后,发现是一个XML文件,里面包含了关于.tmp文件的一些关键点的描述,包括”Range”,”key”,”下载文件的路径”等等.而原本存在于download文件下的下载文件,则被移动到了系统tmp文件夹目录下.而当我们再次进行resume操作的时候,下载文件则又被移回到了download文件夹下。

关于程序被杀掉的断点续传resumeData
根据上面的分析,基本可以得到以下结论:
1.DownloadTask每次进行断点续传的时候,会根据data文件中的”路径Key”去寻找下载文件,然后校验后再根据”Range”属性去进行断点续传。
2.download文件夹中存放的只会是下载中的文件,一旦暂停就会被移动到tmp文件夹下。
3.每个暂停得到的data文件,与下载文件一一对应。
3.断点续传只与tmp文件夹中的文件有关。
  使用NSURLSessionDataTask可以很轻松实现断点续传,可是有个致命的缺点就是无法进行后台下载,一点应用程序进入了后台,便会停止下载。所以无法满足我们的需求。而NSURLSessionDownloadTask是唯一可以实现后台下载的类,所以我们只能从这个类进行下手了。

四、进行后台下载任务
NSURLSession最大的优势在于其后台下载的灵活性,使用如下的代码进行后台数据下载:

 NSURLSessionConfiguration * backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.zyprosoft.backgroundsession"];
    NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com"]];
    NSURLSession *  backgroundSession   = [NSURLSession sessionWithConfiguration:backgroundConfiguration delegate:self delegateQueue:nil];
    [[backgroundSession downloadTaskWithRequest:request]resume];

在下面的回调方法中可以进行下载进度的监听:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    NSLog(@"######");
}

如果在下载过程中点击Home键使应用程序进入后台,NSURLSession的相关代理方法将不再被回调,但是下载任务依然在进行,当后台下载完成后会与AppDelegate进行交互,会调用AppDelegate中的如下方法:

-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{
    NSLog(@"1111");
}

之后应用程序在后台会调用NSURLSesstion代理的如下方法来通知下载结果:

//此方法无论成功失败都会调用
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    NSLog(@"完成:error%@",error);
}
//此方法只有下载成功才会调用 文件放在location位置
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{

}

最后将调用NSURLSesstion的如下方法:

-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{

    NSLog(@"All tasks are finished");

}

代码:
https://github.com/onebutterflyW/HFDownLoad
https://github.com/onebutterflyW/NSURLSession:中包含三个工程
NSURLSession-master是NSURLSession的简单使用,数据的上传下载
WMNSURLSessionHelper是封装的NSURLSession的帮助类,使用completionHandler一次性处理所有数据
SimpleBackgroundTransfer是apple官方后台下载的例子

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员的修养

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值