使用NSURLSession(苹果官方文档翻译)

NSURLSession类以及相关类为通过HTTP下载资源提供了一个API.这个API提供了一系列丰富的代理集合,这些代理方法支持证书和权限管理以及当你的app不再运行或暂停时能够让你的app支持后台下载。
要使用NSURLSession接口,你的app需要创建一系列的会话sessions,每一个session都能够协调一堆相关的数据传输任务。例如,如果你正在写一个web浏览器,你的app需要为每个标签tab或窗口window创建一个session,你的app添加了一系列任务,而每一个任务都代表了一个指定URL的请求(and for any follow-on URLs if the original URL returned an HTTP redirect)。
像大多数网络APIs,NSURLSession APIs是高度异步的。当数据传输完成后,这些数据传输可能是成功也可能是失败的,如果你使用系统默认的代理,那就要利用block进行处理。当然,你也可以选择性的自定义代理方法,这样任务对象task objects就可以调用代理方法处理接收服务器返回的数据(或者数据传输完成时,用于文件下载)。

提示:完成回调主要是为了替代使用自定义委托, 如果你实现了回调方法,那么你就不能再使用代理接收数据。

除了给代理传递信息,NSURLSession接口还提供了状态status和进度progress属性。它支持取消任务、重启任务和暂停任务。它有能够恢复暂停、取消以及因下载失败的暂停(这段翻译的感觉不是特别爽,原文这样,翻译的不对的,希望大家指出The NSURLSession API provides status and progress properties, in addition to delivering this information to delegates. It supports canceling, restarting (resuming), and suspending tasks, and it provides the ability to resume suspended, canceled, or failed downloads where they left off.

理解URL Session的概念

一个会话Session中,任务tasks的行为,取决于三件事情:

  • 会话Session的类型 (由用来创建它的configuration对象的类型决定)

  • 任务task的类型

  • 任务task创建时,应用程序是否在前台

会话sessions的类型

NSURLSessionAPI 支持三种类型的会话session,每种会话session都由创建它的配置对象configuration object决定

  • 默认会话:默认的会话sessions类似其他的用来downloading URLsFoundation方法,他们基于硬盘缓存和在用户钥匙链中的持久化存储证书进行存储。
  • 临时性会话:临时性会话并不在硬盘上存储数据,所有的缓存、证书存储等都存储到内存RAM上并且和会话相关联(这句总觉得别扭)。
  • 后台会话:除了是一个单独的进程处理所有的数据传输外,后台会话很类似于默认会话。后台会话有许多限制,下面会做阐述。

任务Tasks的类型

在一个会话中,NSURLSession类支持三种任务Task类型,分别是:数据任务(data tasks)、下载任务(download tasks)和上传任务(upload tasks)。

  • 1 数据任务:数据任务是用NSData对象发送和接收数据。数据任务是用于经常和服务器进行交互的较短的应用端(App端)请求。每次接收到一块数据,数据任务就可以立即通过回调将一块一块(或者叫一片一片)数据返回给app。
  • 2 下载任务:下载任务是以文件file的形式获取数据,当应用程序不再运行时支持后台下载。
  • 3 上传任务:上传任务是以文件file的形式上传数据,当应用程序不再运行时,支持后台上传。

后台传输数据的注意事项

当应用程序暂停时,NSURLSession类支持后台传输数据。只有通过后台会话配置对象(background session configuration object)创建的会话(sessions)才支持后台传输数据(通过调用这个方法+ (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier创建后台会话配置对象)。
对于后台会话而言,因为实际传输的数据是通过一个独立进程进行的,也因为重启一个应用的进程所需要的代价太大,因此一些功能不能使用,导致以下限制:

  • 1 会话需要用一个代理接收事件(在上传和下载方面,代理在处理数据传输时是相同的);
  • 2 只有HTTPHTTPS协议支持后台数据传输(不支持自定义协议)
  • 3 要有重定向(Redirects are always followed);
  • 4 只支持文件file上传(程序退出后不支持数据data objects上传和流stream上传);
  • 5 当应用进入后台,如果开启后台数据传输,配置对象configuration objectdiscretionary属性被看做是true
提示:在iOS 8 和 OS X 10.10之前,数据任务(data tasks)不支持后台传输数据

在iOS和OS X系统上,在应用重启方面稍微有点不同。
在iOS上,如果一个应用停止运行,那么当后台数据传输完成或者请求认证时,iOS会在应用的UIApplicationDelegate对象里调用application:handleEventsForBackgroundURLSession:completionHandler:方法去重启你的应用。这个方法提供了能够让你的app重启的标识identifier。你的应用应该存储这个完成回调,用同一个标识identifier创建一个后台配置对象,同时根据对应的配置对象configuration object创建一个会话session。这个新的会话能够自动的和后台活动重新关联。之后,当会话完成后台下载任务时,它就会调用代理方法URLSessionDidFinishEventsForBackgroundURLSession:,在这个代理方法中,你可在主线程调用前面存储的完成回调,这样就可以让操作系统知道此时可以安全的挂起suspend你的应用了。
在iOS和OS X上,当用户重启应用时,你的应用应该立刻创建和上次最后运行时尚未完成的那些会话sessions的任务tasks相同的后台配置对象,然后再根据对应的配置对象创建相应的会话。这些会话sessions同样能够自动的和正在进行的后台活动自动关联。

提示:每个标识只能创建一个会话(当你创建这个指定的配置对象时),多个会话共享同一个标识符的行为是不明确的(这句翻译咋还是那么别扭呢?)

当你的应用挂起suspended时,如果一个任务完成,那么就会调用代理方法URLSession:downloadTask:didFinishDownloadingToURL:来处理对应任务的下载文件。
类似的,如果任务是请求认证时,NSURLSession对象会视情况的调用URLSession:task:didReceiveChallenge:completionHandler:或者URLSession:didReceiveChallenge:completionHandler:进行处理。
后台会话中,上传和下载任务在网络出错时能够通过URL加载系统自动的进行重定向。没有必要利用reachabilityAPIs决定何时重试失败的任务。

举一个如何利用NSURLSession进行后台数据传递的例子,可以看这个简单的例子

生命周期和代理交互

在你使用NSURLSession类的基础上,会对你完全理解session的生命周期,包括如何和代理进行交互,代理调用的顺序,当服务器返回一个重定向的时候发生了什么,当下载失败的时候发生了什么等等可能有很大帮助。
为了完全阐述URL session的生命周期,看这个文档
NSCopying Behavior

NSCopying行为

会话session和任务对象task objects遵守以下NSCopying协议:

  • 当你的app拷贝一个会话session和任务task对象时,你得到的是同一个对象。
  • 当你的app拷贝一个配置对象configuration object时,你会得到一个可以自由修改的新的副本。

代理类接口

@import Foundation;
NS_ASSUME_NONNULL_BEGIN
typedef void (^CompletionHandler)();
@interface MySessionDelegate : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSURLSessionStreamDelegate>
@property NSMutableDictionary <NSString *, CompletionHandler>*completionHandlers;
@end
NS_ASSUME_NONNULL_END

创建和配置一个会话

NSURLSessionAPI提供了一个宽泛的配置选择:

  • 在某种程度上,对于指定的一个会话,个人存储支持缓存、cookies、证书以及协议
  • 绑定到一个请求或一组请求的认证(Authentication, tied to a specific request (task) or group of requests (session)
  • 通过URL上传和下载文件,鼓励数据(文件的内容)和元数据(the URL and settings)分离。
  • 每个主机最大连接数的配置
  • 如果一个完整的资源在一定的时间内不能完全下载,那么每个资源的超时设定会触发(翻译的别扭,原话是这样Per-resource timeouts that are triggered if an entire resource cannot be downloaded in a certain amount of time
  • TLS版本支持的最大和最小值
  • 自定义代理字典(Custom proxy dictionaries
  • 控制cookie策略(Control over cookie policies
  • Control over HTTP pipelining behavior(不知道咋翻译)
    因为大部分设置都包含在一个单独的配置对象,您可以重用常用的设置。实例化一个会话对象时,指定以下:
  • 有一个管理会话行为和会话任务的配置对象
  • 当接收和处理指定会话和会话任务的事件时,处理到来数据的代理对象可以选择性的决定是否一个资源加载请求能够转化为下载,等等。
    如果你不提供代理方法,那么系统将使用自带的代理,通过这种方法你就可以和容易的使用NSURLSession替代使用sendAsynchronousRequest:queue:completionHandler:方法的代码(疑问?)
提示:如果你的app需要在后台传输数据,那么需要自定义代理方法。

你初始化session对象后,如果你不再创建一个新的session,那么你就不能改变配置和代理。
1-2展示了如何创建普通、临时和后台会话

// Creating session configurations
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSessionConfiguration *ephemeralConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSessionConfiguration *backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: @"com.myapp.networking.background"];
 // Configuring caching behavior for the default session
NSString *cachesDirectory = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSString *cachePath = [cachesDirectory stringByAppendingPathComponent:@"MyCache"];
/* Note:
 iOS requires the cache path to be
 a path relative to the ~/Library/Caches directory,
 but OS X expects an absolute path.
 */
#if TARGET_OS_OSX
cachePath = [cachePath stringByStandardizingPath];
#endif
NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:16384 diskCapacity:268435456 diskPath:cachePath];
defaultConfiguration.URLCache = cache;
defaultConfiguration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
// Creating sessions
id <NSURLSessionDelegate> delegate = [[MySessionDelegate alloc] init];
NSOperationQueue *operationQueue = [NSOperationQueue mainQueue];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:delegate operationQueue:operationQueue];
NSURLSession *ephemeralSession = [NSURLSession sessionWithConfiguration:ephemeralConfiguration delegate:delegate delegateQueue:operationQueue];
NSURLSession *backgroundSession = [NSURLSession sessionWithConfiguration:backgroundConfiguration delegate:delegate delegateQueue:operationQueue];

除了背景配置外,你可以使用其他的会话配置对象在创建额外的会话sessions(你不能重用背景会话配置,因为如果用一个背景会话配置对象创建了两个背景会话对象后,这样这两个背景会话对象就会共享一个难以识别的 标识了)。你也可以在任意时间安全的修改配置对象configuration objects。当你创建一个会话session,会话session会对配置对象configuration object进行深拷贝,因此修改配置对象configuration object只会影响用它再创建的新对象,不会影响原来的对象。例如,你用改配置对象又创建了一个新会话session,这个新会话只有在wifi状态下才接收数据内容,如下面1-3列表所示。

ephemeralConfiguration.allowsCellularAccess = NO;
NSURLSession *ephemeralSessionWiFiOnly = [NSURLSession sessionWithConfiguration:ephemeralConfiguration delegate:delegate delegateQueue:operationQueue];

用系统提供的代理获取资源(block回调)

请求数据资源最直接的方法是使用系统提供的代理。用系统提供的方法,你需要在你的app中实现两部分代码:

  • 创建一个配置对象以及基于配置对象的会话
  • 实现一个回调,在这个回调中处理接收到的数据

用系统提供的代理方法,每个对应URL的请求,你只需要一段简短的代码,如 1-4:

提示:系统提供的代理方法对自定义网络行为有限制。如果你的app有超出基本URL请求的特殊需求,如:自定义认证或者后台下载,系统提供的代理并不是最优选。要得到一个完整的列表,你需要实现一个完整的代理,你可以看看一个URL会话的生命周期。

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];

用自定义代理方法获取数据

如果你在使用自定义代理去获取数据,你至少要实现以下两个方法:

URLSession:dataTask:didReceiveData: method调用后,如果你的app需要使用返回的数据,那么你需要利用代码以某种方式存储数据。
例如,一个web浏览器可能需要渲染它所已接收到的数据。需要这样做,它可能需要用一个字典用任务对象task和一个可变对象做映射,这个可变数据对象NSMutableData是用来存储接收到的数据的,它可以通过appendData:拼接接收到的新数据。
表1-5展示了如果创建并开启一个数据任务

NSURL *url = [NSURL URLWithString: @"https://www.example.com/"];
NSURLSessionDataTask *dataTask = [defaultSession dataTaskWithURL:url];
[dataTask resume];

下载文件

在较高的层面上,下载一个文件file类似于获取数据,你的app应该实现下列代理方法:

重要提示:在以上方法return之前(我理解为代码块执行完毕之前),你要么必须打开文件读取数据,要么把它移到一个永久性文件夹中。当这个方法return时,如果它仍旧存储在原来的位置,你要把临时文件夹删除掉。

  •   [URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:](https://developer.apple.com/reference/foundation/nsurlsessiondownloaddelegate/1409408-urlsession?language=objc) 为你的应用提供了下载进度的状态信息。
    
  • URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes: 告知你的应用它试图开启以前的失败下载成功了(也就是以前下载失败了,本次又开启了下载状态)。

  • URLSession:task:didCompleteWithError: 告知你的应用下载失败了。

如果你计划在后台会话中下载,那么当你的应用停止运行后下载依然在执行。如果你话在默认会话或者临时会话中下载,那么当你的应用重新运行时你必须重启一个新的下载任务。
在和服务器交互(数据传输)过程中,如果用户想要暂停下载,你可以调用cancelByProducingResumeData:方法。之后(恢复下载后),你的应用可以传递恢复后的返回数据给downloadTaskWithResumeData:downloadTaskWithResumeData:completionHandler:方法,创建新的下载任务继续下载。

如果数据传输失败,你的代理URLSession:task:didCompleteWithError: 方法将会被调用,返回NSError对象。如果任务task是可恢复的,对象的用户信息字典中包含一个以NSURLSessionDownloadTaskResumeDatakey所对应的值,你的应用可以传递恢复后的返回数据给downloadTaskWithResumeData:downloadTaskWithResumeData:completionHandler:方法,创建新的下载任务继续下载。
表1-6提供了一个下载一个比较大的文件的示例
表1-7提供了一个下载任务代理方法的例子

表1-6 下载任务事例

NSURL *url = [NSURL URLWithString:@"https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/ObjC_classic/FoundationObjC.pdf"];
NSURLSessionDownloadTask *downloadTask = [backgroundSession downloadTaskWithURL:url];
[downloadTask resume];

表1-7 下载任务的代理方法

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    NSLog(@"Session %@ download task %@ wrote an additional %lld bytes (total %lld bytes) out of an expected %lld bytes.\n", session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
 
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
    NSLog(@"Session %@ download task %@ resumed at offset %lld bytes out of an expected %lld bytes.\n", session, downloadTask, fileOffset, expectedTotalBytes);
}
 
- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    NSLog(@"Session %@ download task %@ finished downloading to URL %@\n", session, downloadTask, location);
 
    // Perform the completion handler for the current session
    self.completionHandlers[session.configuration.identifier]();
 
   // Open the downloaded file for reading
    NSError *readError = nil;
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:location error:readError];
    // ...
 
   // Move the file to a new URL
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *cacheDirectory = [[fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] firstObject];
    NSError *moveError = nil;
    if ([fileManager moveItemAtURL:location toURL:cacheDirectory error:moveError]) {
        // ...
    }
}

上传正文内容(Uploading Body Content)

你的app(应用 )有3种方式上传HTTPPOST请求的请求体内容,分别是以NSData、file(文件)以及stream(流)方式上传。一般来说,你的app应该这样做:

  • 如果你应用的内存中已经存有data,并且没有什么理由去处理它,那么你就可以以NSData形式上传。
  • 如你你要上传的内容是以file的形式存储在disk(硬盘)上,如果你正在进行后台传输或者是如果为了释放内存中相关的data而将数据写到disk,这样做对你的app更有益的话,那么此时你可以以file形式上传。
  • 如果你是通过网络接收数据的,你可以使用stream

无论你选择哪种方式,只要你使用自定义代理方法,那么你就必须实现URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:方法以获取上传进度信息。

此外,如果你的app以stream的形式上传请求体(request body),那么你必须实现一个自定义的回话代理实现URLSession:task:needNewBodyStream:方法,更详细的信息可以看下面的 “以stream的方式上传正文内容”

1. 以NSData的形式上传正文内容(Uploading Body Content Using an NSData Object)

NSData的形式上传正文内容,你的app需要调用uploadTaskWithRequest:fromData:completionHandler: 方法或者uploadTaskWithRequest:fromData:completionHandler: 方法,请求体数据(request body data)通过参数fromData传入。
会话对象根据数据的长度,计算HTTP头部信息的Content-Length。你的app要额外提供服务器可能需要的头部信息,如内容类型(content type)作为URL请求对象的一部分。

表1-8 以data形式的上传任务事例

NSURL *textFileURL = [NSURL fileURLWithPath:@"/path/to/file.txt"];
NSData *data = [NSData dataWithContentsOfURL:textFileURL];
 
NSURL *url = [NSURL URLWithString:@"https://www.example.com/"];
NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:url];
mutableRequest.HTTPMethod = @"POST";
[mutableRequest setValue:[NSString stringWithFormat:@"%lld", data.length] forHTTPHeaderField:@"Content-Length"];
[mutableRequest setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
 
NSURLSessionUploadTask *uploadTask = [defaultSession uploadTaskWithRequest:mutableRequest fromData:data];
[uploadTask resume];

2. 以file的形式上传正文内容(Uploading Body Content Using a File)

file的形式上传正文内容(body content ),你的app需要调用uploadTaskWithRequest:fromFile:uploadTaskWithRequest:fromFile:completionHandler: 方法创建一个上传任务,你需要提供一个文件路径(file URL)让任务读取body content
会话对象根据数据的长度,计算HTTP头部信息的Content-Length
如果你的app没有为请求头提供Content-Type,那么这个回话会自动提供一个。你的app可能要额外提供服务器需要的头部信息,作为URL请求对象的一部分。

表1-9以file形式的上传任务事例(苹果官网这里写错了)

NSURL *textFileURL = [NSURL fileURLWithPath:@"/path/to/file.txt"];
 
NSURL *url = [NSURL URLWithString:@"https://www.example.com/"];
NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:url];
mutableRequest.HTTPMethod = @"POST";
 
NSURLSessionUploadTask *uploadTask = [defaultSession uploadTaskWithRequest:mutableRequest fromFile:textFileURL];
[uploadTask resume];

3. 以Stream的形式上传正文内容(Uploading Body Content Using a Stream)

stream的形式上传正文内容,你的app需要调用uploadTaskWithStreamedRequest: 去创建上传任务。上传任务将读取正文内容转化为stream,你的app会提供一个含有该stream的请求对象。

你的app需要为请求头提供额外的服务器可能需要的信息,如Content-TypeContent-Length作为URL请求对象的一部分。

此外,由于会话不能倒回去重读以前的stream数据,因此在你的app重新尝试同一个请求时(之前请求过),你需要提供一个新的stream(例如,认证失败时)。可以这样做,你的app需要提供一个 URLSession:task:needNewBodyStream:方法,当这个方法被调用后,无论你的app需要执行什么操作获取或创建一个新的body stream,这个方法的block回调都会返回一个新的stream(这段翻译的感觉不是特别通顺,大家可以看官网,也可以指出我翻译错的地方~~)。

提示: 如果要为文本内容提供stream传输,你的应用必须要实现URLSession:task:needNewBodyStream:代理方法,而这个方法和系统的代理方法是不相容的,也就是说在实现这个方法时不能同时实现系统自带的代理方法。

表1-10以stream的形式上传正文内容事例

NSURL *textFileURL = [NSURL fileURLWithPath:@"/path/to/file.txt"];
 
NSURL *url = [NSURL URLWithString:@"https://www.example.com/"];
NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:url];
mutableRequest.HTTPMethod = @"POST";
mutableRequest.HTTPBodyStream = [NSInputStream inputStreamWithFileAtPath:textFileURL.path];
[mutableRequest setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
[mutableRequest setValue:[NSString stringWithFormat:@"%lld", data.length] forHTTPHeaderField:@"Content-Length"];
 
NSURLSessionUploadTask *uploadTask = [defaultSession uploadTaskWithStreamedRequest:mutableRequest];
[uploadTask resume];

4. 用下载任务上传file

如果使用下载任务上传body content ,那么当创建下载请求时,你的应用只能使用NSDatabody stream作为NSURLRequest object的一部分。
如果你用stream作为传输的数据,你的app需要在认证失败时提供URLSession:task:needNewBodyStream:代理方法以提供新的body stream,这个方法详细阐述在上述3中。
除了数据的返回方式,数据下载任务(download task)的行为很像一个数据任务(data task

note:Kerberos authentication is handled transparently.(直面翻译是Kerberos身份验证是透明处理的,但是不是特别懂,谁懂的可以告知一下)

当一个基于stream传输的上传任务的认证失败时,上传任务就不能重复利用这个stream了(前面说了)。相反,NSURLSession对象代理的URLSession:task:needNewBodyStream: 代理方法获取新的NSInputStream(输入流)对象为新的请求提供body data(如果上传任务的 body(请求体)是由fileNSData对象提供的,那么会话对象将不进行这个调用)。

想更详细了解关于写一个NSURLSessionauthentication delegate(认证代理)方法,可以看Authentication Challenges and TLS Chain Validation

处理iOS的后台活动(Handling iOS Background Activity)

如果你在在iOS中使用了NSURLSession,当一个下载完成时你的app自动重启。你app的代理application:handleEventsForBackgroundURLSession:completionHandler: 要负责创建合适的session以及持有回调block,当session调用它的代理URLSessionDidFinishEventsForBackgroundURLSession: 方法时再调用这个存储的回调block。

表1-11展示了一个创建和开启后台下载任务的事例

NSURL *url = [NSURL URLWithString:@"https://www.example.com/"];
 
NSURLSessionDownloadTask *backgroundDownloadTask = [backgroundSession downloadTaskWithURL:url];
[backgroundDownloadTask resume];

表1-12和1-13展示了分别展示了这些sessons和app delegate 方法

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    AppDelegate *appDelegate = (AppDelegate *)[[[UIApplication sharedApplication] delegate];
    if (appDelegate.backgroundSessionCompletionHandler) {
        CompletionHandler completionHandler = appDelegate.backgroundSessionCompletionHandler;
        appDelegate.backgroundSessionCompletionHandler = nil;
        completionHandler();
    }
 
    NSLog(@"All tasks are finished");
}

表1-13 App后台下载的代理方法

@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (copy) CompletionHandler backgroundSessionCompletionHandler;
 
@end
 
@implementation AppDelegate
 
- (void)application:(UIApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier
  completionHandler:(void (^)())completionHandler
{
    self.backgroundSessionCompletionHandler = completionHandler;
}
 
@end

完结(这部分如果有翻译错误的,各位如果有发现的希望给我指出哈,在此感谢了!!!)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容