您的位置:首页 > 理论基础 > 计算机网络

网络-NSURLSession应用和原理

2016-03-05 16:32 495 查看

网络-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会话 分类

使用share单例获取的全局会话 是系统内部的,所用应用程序都能使用,所以此session不能设置代理监听; 扩展:其实此会话任务不仅不在一个线程,甚至不再一个进程;

使用NSURLSessionConfigraton管理生成session;

defaultSessionConfiguration :进程内会话,用硬盘来缓存数据.账户信息存储到钥匙链,如果有cookie会携带cookie

ephemeralSessionConfiguration: 临时的进程内会话(数据存于内存),不会将cookie,当程序退出数据就会消失;

backgroundSessionConfiguration: 后台会话,相比默认会话,任务是交给后台守护线程完成的,属于别的进程非程序本身,来进行网络数据处理;(所以程序崩溃也不会中断下载,但是如果用户使用多界面强制退关闭程序,Session会断开连接.)

NSURLSessionConfiguration的属性与功能

可以统一添加设置请求头信息
config.HTTPAdditionalHeaders = @{"Authorization":xxxx}


设置主机的最大连接数 :
Config.HTTPMaximumConnectionsPerHost = 5


系统自动选择最佳网络下载:
Config.discretionary=YES;


设置请求超时和缓存策略
requestCachePolicy/Config.timeoutIntervalForRequest=15;


是否允许蜂窝网络下载
Config.allowsCellularAccess=true


等等

数据请求

使用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);
}


注意:

下载任务使异步执行的,即是把下载任务添加到主队列(Session是高度异步的)

所有代理方法都是主线程上异步执行(当指定为主队列时);

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和控制器间会循环引用;

解决方法;

网络操作完成之后,取消操作:[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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: