iOS7新特性-NSURLSession详解
2015-07-23 21:05
399 查看
前言:
本文由DevDiv版主 @jas 原创翻译,转载请注明出处!
原文: http://www.shinobicontrols.com/b … day-1-nsurlsession/
大家都知道,过去的IOS系统网络处理是通过NSURLConnection来实现的。由于NSURLConnection通过全局状态来管理cookies和认证信息,这就意味着在某种情况下,可能同时存在两个不同的连接去使用这些公共资源。NSURLSession很好的解决了许多这种类似的问题。
本文连同附件一共讨论了三种不同的下载场景。本文会着重讲述有关NSURLSession的部分,整个项目就不再阐述了。代码可以在github回购。
NSURLSession状态同时对应着多个连接,不像之前使用共享的一个全局状态。会话是通过工厂方法来创建配置对象。
总共有三种会话:
1. 默认的,进程内会话
2. 短暂的(内存),进程内会话
3. 后台会话
如果是简单的下载,我们只需要使用默认模式即可:
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
配置对象有很多属性。例如,可以设置TLS安全等级,TLS决定你可以使用cookies和超时时间。还有两个非常有趣的属性:allowsCellularAccess和discretionary。前一个属性表示当只有一个3G网络时,网络是否允许访问。设置discretionary属性可以控制系统在一个合适的时机访问网络,比如有可用的WiFi,有充足的电量。这个属性主要针对后台回话的,所以在后台会话模式下默认是打开的。
当我们创建了一个会话配置对象后,就可以用它来创建会话对象了:
1 NSURLSession *inProcessSession;
2 inProcessSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
注意:这里我们把自己设置为代理了。通过代理方法可以告诉我们数据传输进度以及获取认证信息。下面我们会实现一些合适的代理。
数据传输时封装在任务里面的,这里有三种类型:
1. 数据任务 (NSURLSessionDataTask)
2. 上传任务 (NSURLSessionUploadTask)
3. 下载任务(NSURLSessionDownloadTask)
在会话中传输数据时,我们需要实现某一种任务。比如下载:
1 NSString *url = @ “http://appropriate/url/here” ;
2 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; NSURLSessionDownloadTask *cancellableTask = [inProcessSession downloadTaskWithRequest:request];
3 [cancellableTask resume];
现在会话将会异步下载此url的文件内容。
我们需要实现一个代理方法来获取这个下载的内容:
01 - ( void )URLSession:(NSURLSession )session downloadTask:(NSURLSessionDownloadTask )downloadTask didFinishDownloadingToURL:(NSURL *)location
02 - {
03 - // We’ve successfully finished the download. Let’s save the file
04 - NSFileManager *fileManager = [NSFileManager defaultManager];
05 - NSArray *URLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
06 - NSURL *documentsDirectory = URLs[0];
07 - NSURL *destinationPath = [documentsDirectory URLByAppendingPathComponent:[location lastPathComponent]];
08 - NSError *error;
09 - // Make sure we overwrite anything that’s already there
10 - [fileManager removeItemAtURL:destinationPath error:NULL];
11 - BOOL success = [fileManager copyItemAtURL:location toURL:destinationPath error:&error]; if (success)
12 - {
13 - dispatch_async(dispatch_get_main_queue(), ^{
14 - UIImage *image = [UIImage imageWithContentsOfFile:[destinationPath path]]; self.imageView.image = image;
15 - self.imageView.contentMode = UIViewContentModeScaleAspectFill;
16 - self.imageView.hidden = NO; });
17 - }
18 - else
19 - {
20 - NSLog(@ “Couldn’t copy the downloaded file” );
21 - }
22 - if (downloadTask == cancellableTask)
23 - {
24 - cancellableTask = nil;
25 - }
26 - }
这个方法在NSURLSessionDownloadTaskDelegate代理中。在代码中,我们获取到下载文件的临时目录,并把它保存到文档目录下(因为有个图片),然后显示给用户。
上面的代理是下载成功的回调方法。下面代理方法也在NSURLSessionDownloadTaskDelegate代理中,不管任务是否成功,在完成后都会回调这个代理方法。
1 - ( void )URLSession:(NSURLSession )session task:(NSURLSessionTask )task didCompleteWithError:(NSError *)error
2 - {
3 - dispatch_async(dispatch_get_main_queue(), ^{ self.progressIndicator.hidden = YES; });
4 - }
如果error是nil,则证明下载是成功的,否则就要通过它来查询失败的原因。如果下载了一部分,这个error会包含一个NSData对象,如果后面要恢复任务可以用到。
传输进度
上一节结尾,你可能注意到我们有一个进度来标示每个任务完成度。更新进度条可能不是很容易,会有一个额外的代理来做这件事情,当然它会被调用多次。
1 - ( void )URLSession:(NSURLSession )session downloadTask:(NSURLSessionDownloadTask )downloadTask didWriteData:(int64_t)bytesWritten BytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
2 - {
3 - double currentProgress = totalBytesWritten / ( double )totalBytesExpectedToWrite; dispatch_async(dispatch_get_main_queue(), ^{
4 - self.progressIndicator.hidden = NO; self.progressIndicator.progress = currentProgress; });
5 - }
这是NSURLSessionDownloadTaskDelegate的另一个代理方法,我们用来计算进度并更新进度条。
取消下载
NSURLConnection一旦发送是没法取消的。但是,我们可以很容易的取消掉一个NSURLSessionTask任务:
1 - (IBAction)cancelCancellable:(id)sender
2 - {
3 - if (cancellableTask)
4 - {
5 - [cancellableTask cancel];
6 - cancellableTask = nil;
7 - }
8 - }
非常容易!当取消后,会回调这个URLSession:task:didCompleteWithError:代理方法,通知你去及时更新UI。当取消一个任务后,也十分可能会再一次回调这个代理方法URLSession:downloadTask:didWriteData:BytesWritten:totalBytesExpectedToWrite: 。当然,didComplete 方法肯定是最后一个回调的。
恢复下载
恢复下载也非常容易。这里重写了个取消方法,会生成一个NSData对象,可以在以后用来继续下载。如果服务器支持恢复下载,这个data对象会包含已经下载了的内容。
1 - (IBAction)cancelCancellable:(id)sender
2 - {
3 - if (self.resumableTask)
4 - {
5 - [self.resumableTask cancelByProducingResumeData:^(NSData *resumeData)
6 - {
7 - partialDownload = resumeData; self.resumableTask = nil; }];
8 - }
9 - }
上面方法中,我们把待恢复的数据保存到一个变量中,方便后面恢复下载使用。
当新创建一个下载任务的时候,除了使用一个新的请求,我们也可以使用待恢复的下载数据:
01 if (!self.resumableTask)
02 {
03 if (partialDownload)
04 {
05 self.resumableTask = [inProcessSession downloadTaskWithResumeData:partialDownload];
06 }
07 else
08 {
09 NSString *url = @ “http://url/for/image” ;
10 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; self.resumableTask = [inProcessSession downloadTaskWithRequest:request];
11 }
12 [self.resumableTask resume];
13 }
如果我们有这个partialDownload这个数据对象,就可以用它来创建一个新的任务。如果没有,就按以前的步骤来创建任务。
记住:当使用partialDownload创建任务完成后,需要把partialDownload设置为nil。
后台下载
NSURLSession另一个重要的特性:即使当应用不在前台时,你也可以继续传输任务。当然,我们的会话模式也要为后台模式:
1 - (NSURLSession *)backgroundSession
2 - {
3 - static NSURLSession *backgroundSession = nil;
4 - static dispatch_once_t onceToken;
5 - dispatch_once(&onceToken, ^{
6 - NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:@ “com.shinobicontrols.BackgroundDownload.BackgroundSession” ];
7 - backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; });
8 - return backgroundSession;
9 - }
需要非常注意的是,通过给的后台token,我们只能创建一个后台会话,所以这里使用dispatch once block。token的目的是为了当应用重启后,我们可以通过它获取会话。创建一个后台会话,会启动一个后台传输守护进程,这个进程会管理数据并传输给我们。即使当应用挂起或者终止,它也会继续运行。
开启后台下载任务和之前一样,所有的后台功能都是NSURLSession自己管理的。
1 NSString *url = @ “http://url/for/picture” ;
2 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; self.backgroundTask = [self.backgroundSession downloadTaskWithRequest:request]; [self.backgrounTask resume];
现在,即使你按home键离开应用,下载也会在后台继续(受开始提到的配置项控制)。
当下载完成后,你的应用将被重启,并传输内容过来。
将会调用app delegate的这个方法:
1 -( void )application:(UIApplication )application handleEventsForBackgroundURLSession:(NSString )identifier completionHandler:( void (^)())completionHandler
2 {
3 self.backgroundURLSessionCompletionHandler = completionHandler;
4 }
这里,我们获取内容通过completionHandler,当我们接收下载的数据并更新UI时会调用completionHandler。我们保存了completionHandler(注意需要copy),让正在加载的View Controller来处理数据。当View Controller加载成功后,创建后台会话(并设置代理)。因此之前使用的相同代理方法就会被调用。
01 - ( void )URLSession:(NSURLSession )session downloadTask:(NSURLSessionDownloadTask )downloadTask didFinishDownloadingToURL:(NSURL *)location
02 - { // Save the file off as before, and set it as an image view//…
03 - if (session == self.backgroundSession)
04 - {
05 - self.backgroundTask = nil;
06 - // Get hold of the app delegate
07 - SCAppDelegate appDelegate = (SCAppDelegate )[[UIApplication sharedApplication] delegate];
08 - if (appDelegate.backgroundURLSessionCompletionHandler)
09 - { // Need to copy the completion handlervoid (^handler)() = appDelegate.backgroundURLSessionCompletionHandler; appDelegate.backgroundURLSessionCompletionHandler = nil;
10 - handler();
11 - }
12 - }
13 - }
需要注意的几个地方:
1. 不能用downloadTask和self.backgroundTask来比较。因为我们不能确定self.backgroundTask是不是已经有了,有可能是应用新的一次重启。比较session是可行的。
2. 这里使用app delegate来获取completion handler 的。其实,有很多方式来获取completion handler 的。
3. 当保存完文件并显示完成后,如果有completion handler,需要移除然后调用。这个是为了告诉系统我们已经完成了,可以处理新的下载任务了。
本文由DevDiv版主 @jas 原创翻译,转载请注明出处!
原文: http://www.shinobicontrols.com/b … day-1-nsurlsession/
大家都知道,过去的IOS系统网络处理是通过NSURLConnection来实现的。由于NSURLConnection通过全局状态来管理cookies和认证信息,这就意味着在某种情况下,可能同时存在两个不同的连接去使用这些公共资源。NSURLSession很好的解决了许多这种类似的问题。
本文连同附件一共讨论了三种不同的下载场景。本文会着重讲述有关NSURLSession的部分,整个项目就不再阐述了。代码可以在github回购。
NSURLSession状态同时对应着多个连接,不像之前使用共享的一个全局状态。会话是通过工厂方法来创建配置对象。
总共有三种会话:
1. 默认的,进程内会话
2. 短暂的(内存),进程内会话
3. 后台会话
如果是简单的下载,我们只需要使用默认模式即可:
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
配置对象有很多属性。例如,可以设置TLS安全等级,TLS决定你可以使用cookies和超时时间。还有两个非常有趣的属性:allowsCellularAccess和discretionary。前一个属性表示当只有一个3G网络时,网络是否允许访问。设置discretionary属性可以控制系统在一个合适的时机访问网络,比如有可用的WiFi,有充足的电量。这个属性主要针对后台回话的,所以在后台会话模式下默认是打开的。
当我们创建了一个会话配置对象后,就可以用它来创建会话对象了:
1 NSURLSession *inProcessSession;
2 inProcessSession = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
注意:这里我们把自己设置为代理了。通过代理方法可以告诉我们数据传输进度以及获取认证信息。下面我们会实现一些合适的代理。
数据传输时封装在任务里面的,这里有三种类型:
1. 数据任务 (NSURLSessionDataTask)
2. 上传任务 (NSURLSessionUploadTask)
3. 下载任务(NSURLSessionDownloadTask)
在会话中传输数据时,我们需要实现某一种任务。比如下载:
1 NSString *url = @ “http://appropriate/url/here” ;
2 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; NSURLSessionDownloadTask *cancellableTask = [inProcessSession downloadTaskWithRequest:request];
3 [cancellableTask resume];
现在会话将会异步下载此url的文件内容。
我们需要实现一个代理方法来获取这个下载的内容:
01 - ( void )URLSession:(NSURLSession )session downloadTask:(NSURLSessionDownloadTask )downloadTask didFinishDownloadingToURL:(NSURL *)location
02 - {
03 - // We’ve successfully finished the download. Let’s save the file
04 - NSFileManager *fileManager = [NSFileManager defaultManager];
05 - NSArray *URLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
06 - NSURL *documentsDirectory = URLs[0];
07 - NSURL *destinationPath = [documentsDirectory URLByAppendingPathComponent:[location lastPathComponent]];
08 - NSError *error;
09 - // Make sure we overwrite anything that’s already there
10 - [fileManager removeItemAtURL:destinationPath error:NULL];
11 - BOOL success = [fileManager copyItemAtURL:location toURL:destinationPath error:&error]; if (success)
12 - {
13 - dispatch_async(dispatch_get_main_queue(), ^{
14 - UIImage *image = [UIImage imageWithContentsOfFile:[destinationPath path]]; self.imageView.image = image;
15 - self.imageView.contentMode = UIViewContentModeScaleAspectFill;
16 - self.imageView.hidden = NO; });
17 - }
18 - else
19 - {
20 - NSLog(@ “Couldn’t copy the downloaded file” );
21 - }
22 - if (downloadTask == cancellableTask)
23 - {
24 - cancellableTask = nil;
25 - }
26 - }
这个方法在NSURLSessionDownloadTaskDelegate代理中。在代码中,我们获取到下载文件的临时目录,并把它保存到文档目录下(因为有个图片),然后显示给用户。
上面的代理是下载成功的回调方法。下面代理方法也在NSURLSessionDownloadTaskDelegate代理中,不管任务是否成功,在完成后都会回调这个代理方法。
1 - ( void )URLSession:(NSURLSession )session task:(NSURLSessionTask )task didCompleteWithError:(NSError *)error
2 - {
3 - dispatch_async(dispatch_get_main_queue(), ^{ self.progressIndicator.hidden = YES; });
4 - }
如果error是nil,则证明下载是成功的,否则就要通过它来查询失败的原因。如果下载了一部分,这个error会包含一个NSData对象,如果后面要恢复任务可以用到。
传输进度
上一节结尾,你可能注意到我们有一个进度来标示每个任务完成度。更新进度条可能不是很容易,会有一个额外的代理来做这件事情,当然它会被调用多次。
1 - ( void )URLSession:(NSURLSession )session downloadTask:(NSURLSessionDownloadTask )downloadTask didWriteData:(int64_t)bytesWritten BytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
2 - {
3 - double currentProgress = totalBytesWritten / ( double )totalBytesExpectedToWrite; dispatch_async(dispatch_get_main_queue(), ^{
4 - self.progressIndicator.hidden = NO; self.progressIndicator.progress = currentProgress; });
5 - }
这是NSURLSessionDownloadTaskDelegate的另一个代理方法,我们用来计算进度并更新进度条。
取消下载
NSURLConnection一旦发送是没法取消的。但是,我们可以很容易的取消掉一个NSURLSessionTask任务:
1 - (IBAction)cancelCancellable:(id)sender
2 - {
3 - if (cancellableTask)
4 - {
5 - [cancellableTask cancel];
6 - cancellableTask = nil;
7 - }
8 - }
非常容易!当取消后,会回调这个URLSession:task:didCompleteWithError:代理方法,通知你去及时更新UI。当取消一个任务后,也十分可能会再一次回调这个代理方法URLSession:downloadTask:didWriteData:BytesWritten:totalBytesExpectedToWrite: 。当然,didComplete 方法肯定是最后一个回调的。
恢复下载
恢复下载也非常容易。这里重写了个取消方法,会生成一个NSData对象,可以在以后用来继续下载。如果服务器支持恢复下载,这个data对象会包含已经下载了的内容。
1 - (IBAction)cancelCancellable:(id)sender
2 - {
3 - if (self.resumableTask)
4 - {
5 - [self.resumableTask cancelByProducingResumeData:^(NSData *resumeData)
6 - {
7 - partialDownload = resumeData; self.resumableTask = nil; }];
8 - }
9 - }
上面方法中,我们把待恢复的数据保存到一个变量中,方便后面恢复下载使用。
当新创建一个下载任务的时候,除了使用一个新的请求,我们也可以使用待恢复的下载数据:
01 if (!self.resumableTask)
02 {
03 if (partialDownload)
04 {
05 self.resumableTask = [inProcessSession downloadTaskWithResumeData:partialDownload];
06 }
07 else
08 {
09 NSString *url = @ “http://url/for/image” ;
10 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; self.resumableTask = [inProcessSession downloadTaskWithRequest:request];
11 }
12 [self.resumableTask resume];
13 }
如果我们有这个partialDownload这个数据对象,就可以用它来创建一个新的任务。如果没有,就按以前的步骤来创建任务。
记住:当使用partialDownload创建任务完成后,需要把partialDownload设置为nil。
后台下载
NSURLSession另一个重要的特性:即使当应用不在前台时,你也可以继续传输任务。当然,我们的会话模式也要为后台模式:
1 - (NSURLSession *)backgroundSession
2 - {
3 - static NSURLSession *backgroundSession = nil;
4 - static dispatch_once_t onceToken;
5 - dispatch_once(&onceToken, ^{
6 - NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:@ “com.shinobicontrols.BackgroundDownload.BackgroundSession” ];
7 - backgroundSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; });
8 - return backgroundSession;
9 - }
需要非常注意的是,通过给的后台token,我们只能创建一个后台会话,所以这里使用dispatch once block。token的目的是为了当应用重启后,我们可以通过它获取会话。创建一个后台会话,会启动一个后台传输守护进程,这个进程会管理数据并传输给我们。即使当应用挂起或者终止,它也会继续运行。
开启后台下载任务和之前一样,所有的后台功能都是NSURLSession自己管理的。
1 NSString *url = @ “http://url/for/picture” ;
2 NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]]; self.backgroundTask = [self.backgroundSession downloadTaskWithRequest:request]; [self.backgrounTask resume];
现在,即使你按home键离开应用,下载也会在后台继续(受开始提到的配置项控制)。
当下载完成后,你的应用将被重启,并传输内容过来。
将会调用app delegate的这个方法:
1 -( void )application:(UIApplication )application handleEventsForBackgroundURLSession:(NSString )identifier completionHandler:( void (^)())completionHandler
2 {
3 self.backgroundURLSessionCompletionHandler = completionHandler;
4 }
这里,我们获取内容通过completionHandler,当我们接收下载的数据并更新UI时会调用completionHandler。我们保存了completionHandler(注意需要copy),让正在加载的View Controller来处理数据。当View Controller加载成功后,创建后台会话(并设置代理)。因此之前使用的相同代理方法就会被调用。
01 - ( void )URLSession:(NSURLSession )session downloadTask:(NSURLSessionDownloadTask )downloadTask didFinishDownloadingToURL:(NSURL *)location
02 - { // Save the file off as before, and set it as an image view//…
03 - if (session == self.backgroundSession)
04 - {
05 - self.backgroundTask = nil;
06 - // Get hold of the app delegate
07 - SCAppDelegate appDelegate = (SCAppDelegate )[[UIApplication sharedApplication] delegate];
08 - if (appDelegate.backgroundURLSessionCompletionHandler)
09 - { // Need to copy the completion handlervoid (^handler)() = appDelegate.backgroundURLSessionCompletionHandler; appDelegate.backgroundURLSessionCompletionHandler = nil;
10 - handler();
11 - }
12 - }
13 - }
需要注意的几个地方:
1. 不能用downloadTask和self.backgroundTask来比较。因为我们不能确定self.backgroundTask是不是已经有了,有可能是应用新的一次重启。比较session是可行的。
2. 这里使用app delegate来获取completion handler 的。其实,有很多方式来获取completion handler 的。
3. 当保存完文件并显示完成后,如果有completion handler,需要移除然后调用。这个是为了告诉系统我们已经完成了,可以处理新的下载任务了。
相关文章推荐
- IOS键盘收放以及通知
- iOS前期OC训练OC_07NSDate
- iOS前期OC训练OC_省市区字典数组
- iOS前期OC训练OC_通讯录
- iOS前期OC训练OC_06数组
- iOS前期OC训练OC_06字典
- 【iOS】XcodeColors插件与CocoaLumberjack工具
- IOS开发之tableview单选
- iOS逆向笔记第一天
- iOS常见bug-持续记录
- iOS OC -date日期的类的一些关键用法
- 【iOS学习】六、protocol的使用方法
- 自定义iOS7导航栏背景,标题和返回按钮文字颜色
- [iOS Keychain本地长期键值存储]
- iOS OC07_NSDate
- 关于TImer的两种初始化方法总结,自学iOS的哥们过来看看
- ios框架
- 浅思OC的语言的动态特性
- IOS 图片折叠效果实现
- 关于iOS自动布局学习心得