这一篇文章不够全面,推荐查看个人另外一篇文章:URLSession详解git
NSURLSession
提供了一个可供经过网络下载内容的API,而且具备丰富的代理方法。在iOS中,NSURLSession
支持在app未运行或挂起时进行后台下载。此外,NSURLSession
原生的支持data、file、ftp、http和https URL方案,以及用户首选项中代理和socks网关。github
使用NSURLSession
,你的app能够建立一个或多个会话,每个会话协调一组数据传输任务。例如:你正在开发一个浏览器,你能够为每个标签页或窗口建立一个会话,或者一个会话用于交互,一个会话用于后台下载。在每个会话内,能够添加一系列任务,每个任务表明对特定网址的请求。web
会话由建立配置对象时调用的方法决定。能够分为如下几类:json
NSURLConnection
相同的共享cookie存储中。经过调用NSURLSessionConfiguration
类中的defaultSessionConfiguration
方法建立默认会话。NSURLSessionConfiguration
类中的ephemeralSessionConfiguration
建立Ephmeral Session。backgroundSessionConfigurationWithIdentifier:
配置的会话由系统在单独的进程中控制数据传输。在iOS中,backgroundSessionConfigurationWithIdentifier:
配置的会话可使应用程序挂起或终止后依然进行传输数据。若是app是由系统终止并从新启动,app可使用相同标志符建立新配置对象和会话,并恢复到终止时的传输状态;若是用户从多任务屏幕中终止app,系统会取消全部后台传输任务,此外,系统也不会从新启动app,必须由用户启动app后传输任务才会继续。经过调用NSURLSessionConfiguration
类中的backgroundSessionConfigurationWithIdentifier:
建立Background Session。// 建立Shared Session
NSURLSession *sharedSession = [NSURLSession sharedSession]; // 只能在completionHandler的块中获取数据
// 建立Default Session
NSURLSessionConfiguration *defaultSessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultSessionConfiguration delegate:self delegateQueue:nil]; // 在代理方法中获取数据
// 建立Ephemeral Session
NSURLSessionConfiguration *ephemeralSessionConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *ephemeralSession = [NSURLSession sessionWithConfiguration:ephemeralSessionConfiguration]; // 在completionHandler的块中获取数据
// 建立Background Session
NSURLSessionConfiguration *backgroundSessionConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"identifier"];
NSURLSession *backgroundSession = [NSURLSession sessionWithConfiguration:backgroundSessionConfiguration]; // 在completionHandler的块中获取数据
复制代码
会话对象配置还包括对URL缓存和cookie存储对象引用,这些对象用于发出请求、处理响应时使用,具体状况取决于配置和请求类型。api
会话对象会对代理保持强引用,直到你退出app或显式销毁会话。若是没有销毁会话,会形成内存泄漏直至退出app。浏览器
在会话中,建立任务用于上传数据到服务器,而后从服务器取回文件到本地磁盘或NSData对象到内存中,NSURLSession
API提供三种Task类型。缓存
和其余网络请求API同样,NSURLSession
也是异步的,根据你Session配置的不一样,如下面两种方式之一返回数据。bash
completionHandler
的块中获取数据,当传输完成或失败后调用块。Background Session
必须包含一个单独的标志符,标志符不能为nil或空字符串,用于在app崩溃、终止或挂起后将会话再次匹配。resume
方法任务才会执行。学习新的API最好的方法就是使用代码练习,下面经过一个小例子来学习NSURLSession
。服务器
启动Xcode8.2,点击Create a new Xcode project,在iOS模板列表里选择Single View Application模板。cookie
填写工程名称,我这里命名为NSURLSession1,选择文件位置,最后点击Create。
使用NSURLSession
最重要的一点是理解NSURLSession
的实例对象是一切的核心,这个对象处理请求和响应、配置请求、管理会话存储和状态。有多种建立会话的方法,最快速的建立方法是使用NSURLSession
的sharedSession
类方法,以下所示:
- (void)viewDidLoad
{
[super viewDidLoad];
NSURLSession *session = [NSURLSession sharedSession];
}
复制代码
如上面代码所示,在ViewController.m
的viewDidLoad
方法内建立一个session
对象。
咱们将使用session
对象查询iTunes Search API上的软件。iTunes Search API不须要身份验证,所以特别适合咱们的例子。想要查询iTunes Search API咱们须要向 https://itunes.apple.com/search
发送请求、传递参数。建立一个NSURLSessionDataTask
的实例dataTask
用来查询iTunes Search API,查看更新后的viewDidLoad
方法。
- (void)viewDidLoad
{
[super viewDidLoad];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"https://itunes.apple.com/search?term=apple&media=software"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"%@",json);
}];
}
复制代码
在这个实例中,咱们调用dataTaskWithURL: completionHandler:
方法,并传递一个NSURL
的实例和一个完成处理程序。完成处理程序有三个参数,分别是响应的数据(NSData
)、响应对象(NSURLResponse
)和错误(NSError
)。若是请求成功,error
为nil
。由于咱们知道返回的是JSON格式数据,将取回的数据使用JSONObjectWithData: options: error:
方法解析数据并输出到控制台。
使用
NSURLSession
和NSURLConnection
时还有一点须要理解。若是请求失败,完成处理程序中的error
包含错误信息;若是请求返回404
(404表示客户端能够与服务器通讯,但服务器找不到请求内容),则请求成功,只是服务器没有找到请求内容,此时error
为nil
。
全部新建立的任务都是挂起状态,必须调用resume
方法才会执行。更新后的viewDidLoad
以下。
- (void)viewDidLoad
{
[super viewDidLoad];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"https://itunes.apple.com/search?term=apple&media=software"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(@"%@",json);
}];
[dataTask resume];
}
复制代码
运行demo,能够看到控制台输出的数据。
在上一个示例中,经过完成处理处理程序获取返回的数据,另外还能够经过NSURLSessionDownloadDelegate
方法获取数据。下面经过使用NSURLSession
和NSURLSessionDownloadDelegate
获取图片,利用UIProgressView
显示图片下载进度,下载过程当中能够点击Cancel 按钮暂停下载,点击Resume 按钮恢复下载。利用观察者(Key Value Observing)实现不一样状态时Cancel 按钮和Resume 按钮的显示或隐藏状态。
打开Main.storyboard,从组件库(Object Library)中拖拽一个UIImageView
到View Controller
,调整大小至全屏,拖拽一个UIProgressView
到屏幕中心,拖拽UIButton
到左上角有横竖两条辅助虚线位置,右上角一样方法放置一个UIButton
。建立完成后以下图:
拖拽UIImageView
到ViewController.m
的接口部分并命名为imageView
,类型选择为IBOutlet;UIProgressView
在storyboard中不易选中,咱们能够从大纲试图中选中并拖拽到ViewController.m
的接口部分,命名为progressView
,类型选择IBOutlet;分别拖拽左上角和右上角的UIButton
到接口部分,分别命名为cancelButton
、resumeButton
,类型选择为IBOutlet;分别拖拽左上角、右上角两个UIButton
到接口部分,名称分别为cancle
、resume
,类型选择为IBAction。在Attribute Inspector 中修改左上角按钮名称为Cancel,右上角按钮名称为Resume。完成后接口部分代码以下:
#import "ViewController.h"
@interface ViewController ()
@property (strong, nonatomic) IBOutlet UIImageView *imageView;
@property (strong, nonatomic) IBOutlet UIProgressView *progressView;
@property (strong, nonatomic) IBOutlet UIButton *cancelButton;
@property (strong, nonatomic) IBOutlet UIButton *resumeButton;
- (IBAction)cancel:(id)sender;
- (IBAction)resume:(id)sender;
@end
复制代码
点击屏幕右上角的工具栏中的Assistant Editor,以后将编辑器选择拦从Automatic 改成Preview,这样能够预览咱们建立的用户界面。以下图:
点击Preview界面底部的Rotate 按钮,发现屏幕显示的控件都在屏幕左侧。
下面进行自动布局设置。选中imageView
、cancelButton
和resumeButton
点击右下角Resolve AutoLayout Issues,在弹出菜单中选择Selected Views下面的Reset to Suggested Constraints选项;选中progressView
,点击右下角的Align按钮,勾选弹出菜单中的Horizontally in Container和Vertically in Container,点击Add Constraints。若是此时有警告,按照提示选择修复或添加约束。至此,自定义布局完成。
在ViewController.m
接口文件声明session
、downloadTask
,另外,ViewController
须要遵照NSURLSessionDelegate
和NSURLSessionDownloadDelegate
协议。更新后的接口部分以下:
#import "ViewController.h"
@interface ViewController () <NSURLSessionDelegate, NSURLSessionDownloadDelegate>
@property (strong, nonatomic) IBOutlet UIImageView *imageView;
@property (strong, nonatomic) IBOutlet UIProgressView *progressView;
@property (strong, nonatomic) IBOutlet UIButton *cancelButton;
@property (strong, nonatomic) IBOutlet UIButton *resumeButton;
@property (strong, nonatomic) NSURLSession *session;
@property (strong, nonatomic) NSURLSessionDownloadTask *downloadTask;
@property (strong, nonatomic) NSData *resumeData; // 用于存储暂停下载时的数据
- (IBAction)cancel:(id)sender;
- (IBAction)resume:(id)sender;
@end
复制代码
sharedSession
类方法。首先实现会话的取值方法,使用defaultConfiguration
方法建立会话配置,用会话配置实例化会话对象,设定ViewController
为会话的代理,代码以下:- (NSURLSession *)session
{
if (! _session)
{
// 建立会话配置
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 建立会话
_session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
}
return _session;
}
复制代码
实现progressView
的设值方法。由于使用纯代码建立的UIProgressView
默认值是0,使用storyboard建立的UIProgressView
的默认值是0.5,这里须要将UIProgressView
初始值设置为0,否则每次启动时显示下载进度在一半,以后进度再从0开始。
- (void)setProgressView:(UIProgressView *)progressView
{
if (_progressView != progressView)
{
_progressView = progressView;
_progressView.progress = 0;
}
}
复制代码
在viewDidLoad
方法中建立下载任务,下载https://cdn.tutsplus.com/mobile/uploads/2013/12/sample.jpg
的图片。最后调用resume
执行下载任务。
- (void)viewDidLoad
{
[super viewDidLoad];
// 建立下载任务
self.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:@"https://cdn.tutsplus.com/mobile/uploads/2013/12/sample.jpg"]];
// 执行任务
[self.downloadTask resume];
}
复制代码
为了获取返回的数据,咱们须要实现NSURLSessionDownloadDelegate
协议的三个代理方法,URLSession: downloadTask: didFinishDownloadingToURL:
、URLSession: downloadTask: didResumeAtOffset: expectedTotalBytes:
和URLSession: downloadTask: didWriteData: totalBytesWritten: totalBytesExpectedToWrite:
。须要注意的是更新UI时须要回到主线程。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSData *data = [NSData dataWithContentsOfURL:location];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = [UIImage imageWithData:data];
});
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
// 输出NSLog所在行 方法名称
NSLog(@"%d %s",__LINE__ ,__PRETTY_FUNCTION__);
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
float progress = (double) totalBytesWritten / totalBytesExpectedToWrite;
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView.progress = progress;
});
}
复制代码
cancel
点击事件方法包含了点击Cancel按钮后的逻辑操做。若是downloadTask
不为nil
,downloadTask
调用cancelByProducingResumeData
方法,该方法的参数是一个完成块,完成块包含NSData
类型的参数。不是全部的下载任务均可以恢复,因此先检查块的参数resumeData
是否为nil
,不为nil
时将返回的数据保存到ViewController.m
头文件声明的resumeData
中。
- (IBAction)cancel:(id)sender
{
if (! self.downloadTask) return;
[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
if (resumeData)
{
[self setResumeData:resumeData];
[self setDownloadTask:nil];
}
}];
}
复制代码
想要恢复下载很容易,在resume
点击事件中,若是resumeData
不为nil
,使用resumeData
建立一个新的downloadTask
任务,以后执行下载,清空resumeData
数据。
- (IBAction)resume:(id)sender
{
if (! self.resumeData) return;
// 建立任务
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
// 执行任务
[self.downloadTask resume];
// 清除resumeData数据
[self setResumeData:nil];
}
复制代码
点击按钮不该该一直显示,下面使用观察者来决定什么时候显示、隐藏点击按钮。在viewDidLoad
方法中,隐藏点击按钮,将ViewController
添加为reuseData
和downloadTask
关键路径的观察者。
- (void)viewDidLoad
{
[super viewDidLoad];
// 添加观察者
[self addObserver:self forKeyPath:@"resumeData" options:NSKeyValueObservingOptionNew context:NULL];
[self addObserver:self forKeyPath:@"downloadTask" options:NSKeyValueObservingOptionNew context:NULL];
// 隐藏点击按钮
self.cancelButton.hidden = YES;
self.resumeButton.hidden = YES;
// 建立下载任务
self.downloadTask = [self.session downloadTaskWithURL:[NSURL URLWithString:@"https://cdn.tutsplus.com/mobile/uploads/2013/12/sample.jpg"]];
// 执行任务
[self.downloadTask resume];
}
复制代码
在obserValueForKeyPath: ofObject: change: context:
方法中,当resumeData
为nil
时,隐藏Resume按钮;当downloadTask
为nil
时,隐藏Cancel按钮。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"resumeData"])
{
dispatch_async(dispatch_get_main_queue(), ^{
self.resumeButton.hidden = (self.resumeData == nil);
});
}
else if ([keyPath isEqualToString:@"downloadTask"])
{
dispatch_async(dispatch_get_main_queue(), ^{
self.cancelButton.hidden = (self.downloadTask == nil);
});
}
else
// 若是遇到没有观察的属性,将其交由父类处理,父类可能也在观察该属性。
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
复制代码
注意:由于不知道是否在主线程调用
obserValueForKeyPath: ofObject: change: context:
方法,这里更新UI时须要回到主线程。
最后,移除观察者。
- (void)dealloc {
// 移除观察者。
[self removeObserver:self forKeyPath:@"resumeData"];
[self removeObserver:self forKeyPath:@"downloadTask"];
}
复制代码
想要深刻学习观察者,查看KVC和KVO学习笔记这篇文章。
会话对代理是强引用,也就是只要会话是活跃的代理就不会被释放,这样会形成内存泄露,所以任务执行完毕后须要将会话销毁。有多种方法销毁会话,由于这里咱们只下载了一张图片,咱们能够在图片下载完成后销毁会话。下面是更新后的URLSession: downloadTask: didFinishDownloadToURL:
方法,图片下载完成后把Cancle按钮和progressView
设置为隐藏。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSData *data = [NSData dataWithContentsOfURL:location];
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView.hidden = YES; // 下载完成后隐藏进度条
self.cancelButton.hidden = YES; // 下载完成后隐藏Cancel按钮
self.imageView.image = [UIImage imageWithData:data];
});
// 销毁会话
[session finishTasksAndInvalidate];
}
复制代码
经过这篇文章的讲解,你应该对NSURLSession
有一个基本的理解,下一篇文章将经过建立一个简单的播客来使用后台下载。
文件名称:NSURLSession1
源码下载:github.com/pro648/Basi…
参考资料:
欢迎更多指正:github.com/pro648/tips…