Using NSURLSession
NSURLSession API
会话类型
- default session:与其他下载URL的Foundation方法类似,使用基于磁盘的缓存策略,并在用户的keychain中存储证书
- Ephemeral session:不存储任何数据到磁盘中,所有缓存、证书存储都保存在RAM中并与会话绑定,应用结束会话时,这些都会被释放
- background session:类似于default session,除了有一个独立的进程进行来处理所有的数据传输。
- 还有个单例 shared session,没有configuration。
任务类型
- data tasks:使用NSData对象来发送和接收数据。数据任务可以分片返回数据,也可以通过完成处理器一次性返回数据。由于数据任务不存储数据到文件,所以不支持background session
- download tasks:以文件的形式接收数据,当程序不运行时支持后台下载
- upload tasks:通常以文件的形式发送数据,支持background session
后台传输
NSURLSession支持在应用挂起时在后台传输数据,后台传输只由使用background session配置的对象创建的会话进行调用,backgroundSessionConfigurationWithIdentifier:
使用后台会话时,由于其是在一个独立的进程中传输,且重启应用进程相当损耗资源,只有少量特性可以使用,所以有以下限制:
- 会话必须提供事件分发(event delivery)代理
- 只支持http和https协议
- 总是伴随重定向
- 只有从文件中upload tasks才可以,从data objects或者stream将会失败
- 如果当应用在后台时初始化的后台传输,则配置对象的discretionary属性为true
在iOS中,当我们的应用不再运行时,如果后台下载任务完成或者需要证书,则系统会在后台自动重启我们的应用,同时调用UIApplicationDelegate对象的application:handlerEventsForBackgroundURLSession:completionHandler:方法。这个调用会提供启动的应用的session的标识。我们的应用应当存储completion handler,使用相同的标识来创建后台配置对象,然后使用配置对象来创建会话。新的会话会与运行的后台activity关联。当会话完成后台下载任务时,会给会话代理发送一个URLSessioinDidFinishEventsForBackgroundURLSession:消息。代理对象然后在main thread调用存储的completion handler,这样操作系统才知道再次暂停你的应用是安全的。
如果在程序挂起时有任何任务完成,则会调用URLSession:downloadTask:didFinishDownloadingToURL:方法。
同样的,如果任务需要证书,则NSURLSession对象会在适当的时候调用URLSession:task:didReceiveChallenge:completionHandler: 和URLSession:didReceiveChallenge:completionHandler:方法。
会话和任务对象实现了NSCopying协议:
- 当应用拷贝一个会话或任务对象时,会获取相同对象的指针(未创建新对象)
- 当应用拷贝一个配置对象时,会获取一个可单独修改的新的对象(创建新对象)
NSURLSession的生命周期
Life Cycle of a URL Session
中文翻译
创建和配置NSURLSession
配置选项
- 支持对缓存、cookies、证书的私有存储 ,以及对单例会话特定的protocol
- 关联到一个特定请求(任务),或者一组请求(会话)的认证
- 可以通过url上传或者下载文件
- 配置主机的最大连接数
- 当资源无法在一个确定时间内下载时,配置一个超时时间
- 支持安全传输协议TLS的版本区间
- 自定义代理字典
- cookie的控制策略
- HTTP的传输控制
因为大部分的配置都在一个configuration对象中设置,可以重用一些基本设置;你可以在任何时间安全的修改一个configuration对象。因为当创建一个会话时,configuration对象的传递是由深拷贝实现的,所以修改只会影响之后新创建的会话,不会对已存在的会话造成影响。初始化一个会话对象(session object)可以进行如下操作:
- 一个configuration对象用来管理会话或任务的行为
- optional:一个代理对象用来表示接收数据的进度,会话任务或会话其他事件的进度,比如服务器认证,决定一个加载请求是否可转换为下载请求等等
- 如果没有指定一个代理,NSURLSession对象将使用系统默认提供的代理。在这种方式中,你可以轻松的使用NSURLSession方法替代已存在的sendAsynchronousRequest:queue:completionHandler:方法
app在后台传输数据,必须自定义代理
Demo——利用配置项创建NSURLSession
- (void)setUpSession
{
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSessionConfiguration *ephemeralConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSessionConfiguration *backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"backgroundConfiguration"];
//针对defaultConfiguration配置cache
NSString *cacheDirectory = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSString *cachePath = [cacheDirectory stringByAppendingString:@"myCache"];
NSURLCache *cacheUrl = [[NSURLCache alloc]initWithMemoryCapacity:16384 diskCapacity:268435456 diskPath:cachePath];
defaultConfiguration.URLCache = cacheUrl;
defaultConfiguration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
NSOperationQueue *operationQueue = [NSOperationQueue mainQueue];
//创建session
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:self delegateQueue:operationQueue];
//...
}
使用系统提供的代理获取资源
- (void)getDataWithSystemDefaultDelegate
{
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *sessionWithoutADelegate = [NSURLSession sessionWithConfiguration:defaultConfiguration];
NSURL *url = [NSURL URLWithString:@"https://www.example.com/"];
[[sessionWithoutADelegate dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"Got response %@ with error %@.\n", response, error);
NSLog(@"DATA:\n%@\nEND DATA\n", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}] resume];
}
使用自定义的代理获取资源
需要实现如下2个方法:
URLSession:dataTask:didReceiveData:
URLSession:task:didCompleteWithError:
下载文件
app需要实现以下的代理方法
- URLSession:downloadTask:didFinishDownloadingToURL:提供下载内容临时存储的目录地址。注意:在这个方法返回之前,必须打开文件来进行读取或者将下载内容移动到一个永久目录;当方法返回后,临时文件将会被删除。
- URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:提供下载的进度信息
- URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:告诉app尝试恢复之前失败的下载
- URLSession:task:didCompleteWithError:告诉app下载失败
- 当下载任务安排在background session中时,当app停止运行时下载继续;
- 如果是安排在default/ephemeral session中时,当重新开启app时,下载会重新开始。
- 与服务器传输数据期间,如果用户进行了暂停操作,app可以调用cancelByProducingResumeData:方法取消任务。然后,app可以将已传输的数据作为参数传递给downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:来创建一个新的下载任务继续下载。
4)如果传输失败,代理会调用URLSession:task:didCompleteWithError:。如果任务可以再运行,userInfo中包含键NSURLSessionDownloadTaskResumeData,可以将数据作为参数传递给downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:来创建一个新的下载任务进行重试
上传数据内容
app可以对Http post请求提供三种方式的请求体:NSData对象、文件、数据流
NSData上传
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(NSData *)bodyData;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(NSData *)bodyData
completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
file上传
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
stream上传
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler;
后台任务
使用NSURLSession,当下载完成时,app将会自动启动;代理中2个主要方法:
//负责创建session,存储completionHandler
- (void)application:(UIApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier
completionHandler:(void (^)(void))completionHandler;
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session;