网络-NSURLSession应用和原理

网络-NSURLSession

1. 简介

NSRULConnection使用runloop来达到异步下载的,原理:Runloop保证重要的任务流畅执行; 分配固定时隙,实现单一线程异步;
connection 应用了runloop ,苹果不推荐使用底层设计理念,所以用封装更好NSURLSession;

NSURLSession 提供了配置会话缓存,协议,cookie和证书能力,这使得网络架构和应用程序可以独立各种,互不干扰; 还可以做会话任务,能加载数据,进行文件的上传和下载分别对应:DataTask,UploadTask,DownloadTask. 会话任务使默认挂起的,需要resume开始执行;

NSURLConnection完成的三个主要任务:获取数据(通常是JSON、XML等)、文件上传、文件下载。其实在NSURLSession时代,他们分别由三个任务来完成:NSURLSessionData、NSURLSessionUploadTask、NSURLSessionDownloadTask,这三个类都是NSURLSessionTask这个抽象类的子类
相对于NSURLConnection, NSURLSession支持任务的暂停,取消,恢复,并且默认任务运行在非主线程上

Session会话 分类
  1. 使用share单例获取的全局会话 是系统内部的,所用应用程序都能使用,所以此session不能设置代理监听; 扩展:其实此会话任务不仅不在一个线程,甚至不再一个进程;
  2. 使用NSURLSessionConfigraton管理生成session;
    • defaultSessionConfiguration :进程内会话,用硬盘来缓存数据.账户信息存储到钥匙链,如果有cookie会携带cookie
    • ephemeralSessionConfiguration: 临时的进程内会话(数据存于内存),不会将cookie,当程序退出数据就会消失;
    • backgroundSessionConfiguration: 后台会话,相比默认会话,任务是交给后台守护线程完成的,属于别的进程非程序本身,来进行网络数据处理;(所以程序崩溃也不会中断下载,但是如果用户使用多界面强制退关闭程序,Session会断开连接.)
NSURLSessionConfiguration的属性与功能
  1. 可以统一添加设置请求头信息config.HTTPAdditionalHeaders = @{"Authorization":xxxx}
  2. 设置主机的最大连接数 : Config.HTTPMaximumConnectionsPerHost = 5
  3. 系统自动选择最佳网络下载:Config.discretionary=YES;
  4. 设置请求超时和缓存策略requestCachePolicy/Config.timeoutIntervalForRequest=15;
  5. 是否允许蜂窝网络下载Config.allowsCellularAccess=true
  6. 等等

数据请求

使用Data任务

//1.创建请求
   NSURLrequest *request = [NSURLRequest requestWithURL:url];
//2.创建会话 (此处全局会话)  
    NSURLSession *session=[NSURLSession sharedSession];
    //2.1 从会话创建Data任务  后面需要开启任务
    NSURLSessionDataTask *dataTask=[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    if (!error) {
        id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
            NSLog(@"%@",result);
        }else{
            NSLog(@"error is :%@",error.localizedDescription);
        }
    }];
    [dataTask resume];    //启动任务
}
//可以精简为
    [[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
    }] resume];

文件下载

使用NSURLSessionDownloadTask 下载文件的过程与前面差不多,需要注意的是文件下载文件之后会自动保存到一个临时目录(temp),需要开发人员自己将此文件重新放到其他指定的目录中。 或者直接在内存里面显示;, 默认异步的;

1. 内存暴涨问题解决

NSURLSession 自动不会出现内存暴涨情况,

//全局会话 创建 downLoadTask任务;
//参数location: 下载完成后文件路径 (需要我们重新指定)
    [[[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
    //重新指定路径
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    path = [path stringByAppendingPathComponent: fileName];
    //复制文件过去
    [[NSFileManager defaultManager] copyItemAtPath:location.path toPath:path error:NULL];
    }] resume]; //启动任务
下载图片并显示

downLoad 不会占硬盘缓存 , Data会生成缓存;所以下载显示用download ;

NSURL *url = [NSURL URLWithString:@“http:/127.0.0.1/uploads/xx.png"];

[[self.session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{

            NSData *data = [NSData dataWithContentsOfURL:url];

            self.imageView.image = [UIImage imageWithData:data];
        });
    }] resume];
//可以设置在缓存文件夹,并且设置MemoryCapactity 空间大小 和 diskCapacity 缓存空间大小
NSURLCache *cache = [NSURLCache alloc] initWithMemoryCapacity: 1024*1024 diskCapacity:1024*1024*5 diskPath:@"images"];
[ NSURLCache setSharedURLCache : cache];
2. 下载进度问题

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

//懒加载Session 并设置代理
- (NSURLSession *)session{
    if (_session == nil) {
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];// 创建session 指定的队列 ,决定了代理和block的执行队列. nil表示默认新队列;
    }
    return _session;
}
/
//使用非全局session
    [[self.session downloadTaskWithURL:url] resume];

代理方法如下:

//下载完成
- (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);
}

注意:

  1. 下载任务使异步执行的,即是把下载任务添加到主队列(Session是高度异步的)
  2. 所有代理方法都是主线程上异步执行(当指定为主队列时);
3. 断点续传 -下载的暂停取消继续.

记录下下载任务 downLoadtask.
取消: cancel .不能续传

暂停/挂起: cancelByProducinResumeData

self.downloadTask cancelByProducinResumeData:^(NSData *resumeData) {
    self.resumeData = resumeData; //记录续传数据
    self.resumeData writeToFile:self.path atomically:YES]; //暂停 要存储当前数据到文件;// 只是一个配置信息;方便续传时获得range 值;
}
    //进行保存沙盒操作等
    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其实是plist文件,只是几率下载的信息,如url,path等,而不是存储的下载数据;它之所以可以续传,是因为存储了断点续传核心range头.
2. loadTask 下载完会删除,但是和connection 的 DownloaderDelegate 的产权保护删除不同,后者是整个过程都不能获取下载数据;
3. 如果突然停电,reumeData的range数据不能保存,但是可以从已经下载到的文件获取到filesize,再解析plist文件,给reumeData重设range;(少见)
4. 文件下载完成解压缩 程序自动解压,使用SSZipArchive框架.(注意:用词框架需要导入系统一个库 liba.tbd . 程序设置最后一行)


扩展: 后台下载

使用后台会话进行下程序退出到后台也能正常下载完成,但是程序在后台UI无法更新,不能获取进度;这时,我们需要通过应用程序代理进行UI更新,原理如图:

当NSURLSession在后台开启几个任务之后,如果其中有任务完成,系统就会会调用此APP的代理方法:-(void)application: (UIApplication * ) application handleEventsForBackgroundURLSession:identifier completionHandler:(^());completionHandlr里进行完成的操作. 通常我们会保持此对象,直到最后一个任务完成;
此时会重新通过会话标识(config中设置的)找到对应会话并调用NSURLSession的-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession * )session代理方法例进行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();
    }
}

Session的文件上传

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];
}
上传进度条

使用代理方法获取.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;
}
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];
}

NSURLSession的使用注意

一般session代理设控制器,那么self.session和控制器间会循环引用;

解决方法;
  1. 网络操作完成之后,取消操作:[session finishTaskAndInvalidate]; 并 session =nil;
    • 因为session invalidate之后无法再次使用,所以设为nil之后,下次使用时会懒加载创建;
    • 但是这样实现,每一次操作完成都会创建销毁session,麻烦,所以我们要把解决代码下载ViewWillDisapper方法里,这样只有在控制器释放时候才销毁,这样不会重复创建销毁session;

完整代码如下:

- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    //一旦销毁,session和代理之间的引用,session和block之间的关系就被干掉
    //所以无法再次使用
    [self.session invalidateAndCancel];
    self.session = nil;
}

你可能感兴趣的:(iOS网络)