iOS-NSURLSession的理解和总结


-Session会话 分类
-请求数据
-文件下载
-下载进度
-断点续传 -下载的暂停取消继续
-后台下载
-文件上传
-重点总结


  • Session会话 分类

1️⃣NSURLResponse 为服务器的响应,真实类型为NSHTTPURLResponse,通常只在“下载”功能时,才会使用。当链接是FTP服务时NSURLResponse的真实类型不为NSHTTPURLResponse
2️⃣全局session和自定义session

  • 全局session:使用share单例获取的全局会话 是系统内部的,所用应用程序都能使用,所以此session不能设置代理监听; 扩展:其实此会话任务不仅不在一个线程,甚至不再一个进程;
  • 自定义session:使用NSURLSessionConfigraton管理生成session;
    1、defaultSessionConfiguration :进程内会话,用硬盘来缓存数据.账户信息存储到钥匙链,如果有cookie会携带cookie
    2、ephemeralSessionConfiguration: 临时的进程内会话(数据存于内存),不会将cookie,当程序退出数据就会消失;
    3、backgroundSessionConfiguration: 后台会话,相比默认会话,任务是交给后台守护线程完成的,属于别的进程非程序本身,来进行网络数据处理;(所以程序崩溃也不会中断下载,但是如果用户使用多界面强制退关闭程序,Session会断开连接.)

3️⃣NSURLSessionConfiguration的常用属性与功能

可以统一添加设置请求头信息config.HTTPAdditionalHeaders = @{"Authorization":xxxx}
设置主机的最大连接数 : Config.HTTPMaximumConnectionsPerHost = 5
系统自动选择最佳网络下载:Config.discretionary=YES;
设置请求超时和缓存策略requestCachePolicy/Config.timeoutIntervalForRequest=15;
是否允许蜂窝网络下载Config.allowsCellularAccess=true

  • 请求数据
  NSURL *url = [NSURL URLWithString:@"http://imgsrc.baidu.com/image/c0%3Dshijue1%2C0%2C0%2C294%2C40/sign=55d62af8a164034f1bc0ca45c7aa1344/79f0f736afc379311478620ce1c4b74543a91186.jpg"];
  NSURLRequest *request = [NSURLRequest requestWithURL:url];
  NSURLSession *session = [NSURLSession sharedSession];   //全局session
  NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
      //当前block线程为子线程
      if (error) {
          NSLog(@"下载失败");
      }else{
          dispatch_async(dispatch_get_main_queue(), ^{
              UIImage *image = [UIImage imageWithData:data];
              self.imageview.image = image;
          });
      }
  }];
  [task resume];
  • 文件下载

1、使用NSURLSessionDownloadTask进行文件下载,过程和上面数据请求差不多,需要注意的是文件下载之后会自动保存到一个临时目录(temp),需要开发人员自己将此文件重新放到其他指定的目录中。 或者直接在内存里面显示,block默认异步的。
2、使用session下载文件不会出现内存暴涨的情况,他是每下载一点就会将内存当中的数据转移到temp文件夹里,所以内存不会出现暴涨的情况
3、NSURLSessionDownloadTask在下载过程中会占用缓存,下载完毕后不会占用缓存
4、NSURLSessionDataTask会一直占用缓存,所以一般的下载功能用DownloadTask(这里只是一般情况,下面有更为详细的总结)

    NSURL *url = [NSURL URLWithString:@"http://imgsrc.baidu.com/image/c0%3Dshijue1%2C0%2C0%2C294%2C40/sign=55d62af8a164034f1bc0ca45c7aa1344/79f0f736afc379311478620ce1c4b74543a91186.jpg"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSession *session = [NSURLSession sharedSession];   //全局session
    NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        //当前block线程为子线程
        if (error) {
            NSLog(@"下载失败");
        }else{
            //重新指定路径
            NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
            path = [path stringByAppendingPathComponent:response.suggestedFilename];
            //复制文件过去
            [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:path error:NULL];
            dispatch_async(dispatch_get_main_queue(), ^{
                UIImage *image = [UIImage imageWithContentsOfFile:path];
                ...  //设置图片
            });
        }
    }];
    [task resume];
  • 下载进度

使用NSURLSessionDownloadDelegate代理方法( 其中关系:NSURLSessionDownloadDelegate —> NSURLSessionTaskDelegate –> NSURLSessionDelegate
因为不能使用全局Session设置代理,所以要监听进度的话,上面代码的Session需要更换;可以使用NSURLSessionConfiguration来创建;且注意不能使用带block回调的方法,代理方法优先级比block回调低,会被block覆盖(下面会有总结)

- (void)viewDidLoad {
    [super viewDidLoad];
    //创建任务开始任务
    NSURL *url = [NSURL URLWithString:@"http://imgsrc.baidu.com/image/c0%3Dshijue1%2C0%2C0%2C294%2C40/sign=366390e100d162d991e36a5f79b6c399/f3d3572c11dfa9ecbe78883268d0f703918fc145.jpg"];
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    // 创建session 指定的队列 ,决定了代理和block的执行队列. nil表示默认新队列;
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
    [[session downloadTaskWithURL:url] resume];    //使用不带block的方法
}

//下面是代理方法
//下载完成
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    NSLog(@"%@",[NSThread currentThread]);
    NSLog(@"下载完成 : %@",location);
}
//续传的方法
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
    NSLog(@"续传");
}
//获取进度的方法  (多次调用获取进度)
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    float process = totalBytesWritten * 1.0 /totalBytesExpectedToWrite;
    NSLog(@"下载进度: %f",process);
}
  • 断点续传 -下载的暂停取消继续

取消: cancel .不能恢复续传
暂停/挂起: cancelByProducinResumeData,可以利用保存的resumeData续传

self.downloadTask cancelByProducinResumeData:^(NSData *resumeData) {
    self.resumeData = resumeData; //记录续传数据
    //暂停 要存储当前数据到文件;// 只是一个配置信息;方便续传时获得range 值;
    self.resumeData writeToFile:self.path atomically:YES]; 
}
//进行保存沙盒操作等
self.downloadTask = nil; //防止点击多次,暂停多次清空resumeData数据

恢复续传

//续传,先加载暂停的数据
if(self.downloadTask !=nil ){//判断如果没点暂停就不执行继续下载.
    return ; 
}
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:self.path]) {
    //如果文件存在
    self.resumeData = [NSData dataWithContentsOfFile:self.path];
}
if (self.resumeData == nil) {
    return;
}
//继续下载
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData ];
[self.downloadTask resume];
self.resumeData = nil; //防止点击多次, 创建多个续传

reumeData

reumeData其实是plist文件,只是记录下载的信息,如url,path等,而不是存储的下载数据;它之所以可以续传,是因为存储了断点续传核心range头.

  1. loadTask 下载完会删除,但是和connection 的 DownloaderDelegate 的产权保护删除不同,后者是整个过程都不能获取下载数据;
  2. 如果突然停电,reumeData的range数据不能保存,但是可以从已经下载到的文件获取到filesize,再解析plist文件,给reumeData重设range;(少见)
  3. 文件下载完成解压缩 程序自动解压,使用SSZipArchive框架.(注意:用词框架需要导入系统一个库 liba.tbd . 程序设置最后一行)

  • 后台下载

1、backgroundSessionConfiguration使用后台会话进行下程序退出到后台也能正常下载完成,但是程序在后台UI无法更新,不能获取进度,我们需要通过应用程序代理进行UI更新
2、当NSURLSession在后台开启几个任务之后,如果其中有任务完成,系统就会会调用此APP的代理方法-(void)application: handleEventsForBackgroundURLSession: identifier completionHandler:(^());
在completionHandlr里进行完成的操作. 通常我们会保持此对象,直到最后一个任务完成
此时会重新通过会话标识(config中设置的)找到对应会话并调用NSURLSession的
-(void)URLSessionDidFinishEventsForBackgroundURLSession:代理方法进行UI的更新.并调用completionHandler通知系统已经完成所有操作。

-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{

    //backgroundSessionCompletionHandler是自定义的一个属性
    self.backgroundSessionCompletionHandler=completionHandler;   
}

-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

    //Other Operation....

    if (appDelegate.backgroundSessionCompletionHandler) {
        void (^completionHandler)() = appDelegate.backgroundSessionCompletionHandler;     
        appDelegate.backgroundSessionCompletionHandler = nil;        
        completionHandler();
    }
}
  • 文件上传

put直接以文件的方式写入 ;
post需要服务器端脚本支持 ;

  1. 通过session发送put请求上传文件.

如果直接上传返回状态码401 –没有授权;PUT需要授权身份验证;
请求头有一项Authorization: Basic YWRtaW46MTIzNDU2 ;base64编码的账号和密码;
PUT方式,上传,如果服务器没有此数据,那么会创建,如果有同名,会更新;
如果用post,要设置Content-Type、Range、User-Agent、Authorization

    NSURL *url = [NSURL URLWithString:@"http:/192.168.31.244/uploads/123.png"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"put";
    //添加请求头的授权信息 Authorization: Basic YWRtaW46MTIzNDU2
    [request setValue:[self getAuthorizationStr] forHTTPHeaderField:@"Authorization"]; //getAut方法自定义编码账号,格式:admin:123456
    //获取文件路径
    NSString *path = [[NSBundle mainBundle] pathForResource:@"xx" ofType:@"png"];
    //创建上传任务
    [[self.session uploadTaskWithRequest:request fromFile:[[NSURL alloc] initFileURLWithPath:path] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        NSLog(@"%@   %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding],response);
    }] resume];

2.上传进度条

使用代理方法获取.NSURLSessionTaskDelegate;

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
    //bytesSent  本次上传的字节数
    //totalBytesSent  总共上传的字节数
    //totalBytesExpectedToSend  文件的总大小
    float process = (float)totalBytesSent / totalBytesExpectedToSend;
}

3.Delete方式删除文件

- (void)deleteFile{    
    NSURL *url = [NSURL URLWithString:@"http:/127.0.0.1/uploads/123.png"];    
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];    
    request.HTTPMethod = @"delete";    
    //添加请求头的授权信息 Authorization: Basic YWRtaW46MTIzNDU2    
    [request setValue:[self getAuthorizationStr] forHTTPHeaderField:@"Authorization"];    
    [[self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {        
        NSLog(@"%@  %@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding],response);    
    }] resume];
}

  • 重点总结

1️⃣NSURLSession的使用注意
一般session代理设为控制器,而session的代理是强引用,那么self.session和控制器之间会造成循环引用。在session不用时要使用取消操作
[session finishTaskAndInvalidate];
self.session = nil;

2️⃣NSURLSessionDownloadTask的特点

使用NSURLSessionDownloadTask进行文件下载,每下载一点数据,系统就会将该数据从内存转移到磁盘的临时目录当中(这里内存只做了中转站,没有长期保存数据),下载完成之后需要开发人员自己将已下载好的文件重新放到其他指定的目录中,这样做的优点是内存不会暴涨,适合小文件的下载,但是要实现真正意义上的大文件断点下载有几点不足。
缺点一:获取不到每次下载的小数据块
缺点二:下载完的数据存放在临时目录,这个地址要在下载完毕时才会给我们
缺点三:非正常的结束程序会丢失下载进度,也可能丢失已下载的那部分文件
NSURLSessionDownloadTask在下载过程中会占用缓存,下载完毕后不会占用内存

3️⃣NSURLSessionDataTask的特点

使用NSURLSessionDataTask进行文件下载有block和代理两种方式

方式一:block方式,这种方式会等到文件下载完毕时将下载的文件通过block的data对象传给我们,这个时候整个文件都会在内存当中,当文件很大时就会造成内存暴涨。
[[session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}] resume];
方式二:代理方式,通过设置session的代理为self,遵守NSURLSessionDataDelegate协议,在代理方法中可以得到每次下载下来的数据片段,它是以数据片段的形式给我们传递数据,不会再内存当中长期保存,内存不会有太大的变动。通过手动拼接数据就可以实现大文件的断点下载。
//接收到服务器返回的数据片段(这个方法可能会被调用N次)
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
}

需要注意的是,block方式的优先级大于代理方法的优先级,block会覆盖代理,导致代理方法不调用。所以说在用block方式时不要实现代理方法,在使用代理方法时不要使用block方法,要用下面这个非block方法来生成dataTask

[[session dataTaskWithURL:url] resume];  //不带block的方法

4️⃣dataTask下载时的内存变化情况:

非block方法 + 代理方法  = 内存略微增长
非block方法            = 内存几乎不变
block方法              = 内存的涨动幅度等于文件的大小
block方法   + 代理方法   = 内存的涨动幅度等于文件的大小  (block会使代理方法无效)

5️⃣在选择协议的时候要注意,所选的协议要和创建的Task相符,在session设置代理时并没有限定代理要遵守哪个协议,而是根据后面Task的类型选择不同的协议。
NSURLSessionDownloadTask对应NSURLSessionDownloadDelegate协议
NSURLSessionDataTask对应NSURLSessionDataDelegate协议
NSURLSessionUploadTask对应NSURLSessionTaskDelegate协议

6️⃣最后再次总结一下NSURLSessionDownloadTask和NSURLSessionDataTask下载文件的特点,以下四种情况要根据开发的具体情况选择。

  • NSURLSessionDownloadTask的block方式:只有文件下载完毕后才会通过block当中的location对象来告诉我们已下载的文件地址,我们获取不到下载进度,也得不到每次下载下来的数据片段。由于每下载一点数据都会转移到磁盘当中,所以内存不会有很大的消耗。
  • NSURLSessionDownloadTask的代理方式:我们可以通过代理方法获得下载进度以及下载完毕后的文件存放路径�,但是我们同样获取不到每次下载的数据片段。内存情况同上,不会有很大消耗
  • NSURLSessionDataTask的block方式:只有当文件下载完毕时才会通过block当中的data对象将已下载的文件给我们,我们获取不到下载进度,也得不到每次下载下来的数据片段。由于这个data数据是存放在内存当中的,所以当data数据很大时内存会暴涨。
  • NSURLSessionDataTask的代理方式:我们可以通过代理方法获得每次下载下来的数据片段,通过数据拼接我们可以得到完整的数据,也可以间接计算出下载进度。这些数据片段不会在内存当中长期存在,所以不会出现内存暴涨的情况。

后续不断更新修复。。

你可能感兴趣的:(iOS-NSURLSession的理解和总结)