iOS7 Networking with NSURLSession: Part 1 – iPhone手機開發技術文章 iPhone軟體開發教學課程

From a developer’s perspective, one of the more significant changes in iOS 7, and OS X Mavericks for that matter, is the introduction of NSURLSession.
Even thoughNSURLSession may seem daunting at first glance,
it’s important that you understand what it is, how it relates to NSURLConnection,
and what the differences are. In this series, I will take you through the fundamentals of NSURLSession so
you can take advantage of this new technology in your own applications.

從開發者的角度,對於iOS7和OS X Mavericks最顯著的變化就是引入瞭 NSURLSession 。雖然乍一看,NSURLSession 有點令人生畏,但重要的是要瞭解它是什麼,它和 NSURLConnection 有什麼關系和差異。在本系列 Networking with NSURLSession 教程中,我將會帶你瞭解 NSURLSession 有關的基本內容,你也可以在你的應用程序中使用這種新技術的優勢。

Why Replace NSURLConnection?

The first question you may be asking yourself is why Apple found it necessary to introduce NSURLSession while
we are perfectly happy with NSURLConnection. The real question
is whether you are happy with NSURLConnection.
Do you remember that time you were cursing and throwing things at NSURLConnection?
Most Cocoa developers have been in that place, which is why Apple decided to go back to the drawing board and create a more elegant solution, better suited for the modern web.

你可能會問這樣一個問題:為什麼正當我們愉快的使用 NSURLConnection的時候,有必要引入 NSURLSession 嗎?然而,問題恰恰就在於,你是否真的使用 NSURLConnection 很愉快?莫非你忘瞭你曾經咒罵吐槽
NSURLConnection 瞭,沒錯,大多數 Cocoa 開發者曾有這樣的經歷,所以Apple才決定重新設計一種更優雅的解決方案以適應於現代網絡開發。

Even though NSURLSession and NSURLConnection have
a lot in common in terms of how they work, at a fundamental level, they are quite different. Apple created NSURLSessionto
resemble the general concepts of NSURLConnection, but you
will learn in the course of this series that NSURLSession is
modern, easier to use, and built with mobile in mind.

盡管 NSURLSession 和 NSURLConnection 的運行有很多共同點,但是在最基礎的層面上,它們還是有著很大的不同。Apple 引入類似於 NSURLConnection 概念的 NSURLSession ,你將會在本系列 Networking with NSURLSession 教程的這篇文章中瞭解到 NSURLSession 是現代的,更易於使用的,而且是附有為移動應用設計的初衷。

What is NSURLSession?

Before I discuss the differences between NSURLSession and NSURLConnection,
it’s a good idea to first take a closer look at what NSURLSession is.
Despite its name,NSURLSession isn’t just another class
you can use in an iOS or OS X application.NSURLSession is
first and foremost a technology just like NSURLConnection is.

在討論 NSURLSession 和 NSURLConnection 的不同之處之前,不妨先仔細瞭解一下 NSURLSession 是什麼。對於 NSURLSession ,它僅僅是可以在 iOS 和 OS X應用程序中使用的一個類,其就像 NSURLConnection 一樣是一種用於網絡開發的技術。

Sessions are Containers

NSURLSession and NSURLConnection both
provide an API for interacting with various protocols, such as HTTP and HTTPS.
The session object, an instance of theNSURLSession class,
is what manages this interaction. It is a highly configurable container with an elegant API that allows for fine-grained control. It offers features that are absent in NSURLConnection.
What’s more, with NSURLSession, you can accomplish tasks
that are simply not possible with NSURLConnection, such
as implementing private browsing.

NSURLSession 和 NSURLConnection 都提供瞭與各種協議,諸如 HTTP 和 HTTPS ,進行交互的API。會話對象,NSURLSession 類對象,就是用於管理這種交互過程。它是一個高度可配置的容器,通過使用其提供的APPI,可進行細粒度的管理控制。它提供瞭在 NSURLConnection 中的所有特性,此外,它還可以實現 NSURLConnection 不能完成的任務,例如實現私密瀏覽。


The basic unit of work when working with NSURLSession is
the task, an instance ofNSURLSessionTask. There are three
types of tasks, data tasks, upload tasks, anddownload tasks.

使用 NSURLSession 最基本單元就是任務(task),這個是 NSURLSessionTask 的實例。有三種類型的任務:data task,upload task 和 download task。

  • You”ll most often use data tasks, which are instances of NSURLSessionDataTask.
    Data tasks are used for requesting data from a server, such as JSON data. The principal difference with upload and download tasks is that they return data directly to your application instead of going through the file system. The data is only stored in memory.
    最常使用的就是 Data task,它是 NSURLSessionDataTask 的實例。Data task 是用於從服務器上請求數據,例如 JSON 數據。它和 Upload task 和 Download task 的主要區別是它直接返回數據給應用程序,而非通過文件系統。這些數據隻存儲在內存中。

    • As the name implies, upload tasks are used to upload data to a remote destination. The NSURLSessionUploadTask is
      a subclass of NSURLSessionDataTaskand behaves in a similar
      fashion. One of the key differences with a regular data task is that upload tasks can be used in a session created with a background session configuration.
      顧名思義,Upload task 是用來將數據上傳到遠程的目標。NSURLSessionUploadTask 是 NSURLSessionDataTask 的子類,而且行為也類似。其中一個主要的區別於普通的Data task的是 Upload task 可以在後臺會話管理中使用。

      • Download tasks, instances of NSURLSessionDownloadTask,
        inherit directly fromNSURLSessionTask. The most significant
        difference with data tasks is that a download task writes its response directly to a temporary file. This is quite different from a regular data task that stores the response in memory. It is possible to cancel a download task and resume it
        at a later point.
        Download task,NSURLSessionDownloadTask 的實例,直接繼承自 NSURLSessionTask。和Data task最顯著的區別是 Download task 將其響應數據寫入臨時文件中,而 Data task 隻是將其響應數據保存在內存中。Download
        task 可以取消,也可以在稍後進行恢復。

        As you can imagine, asynchronicity is a key concept in NSURLSession.
        TheNSURLSession API returns data by invoking a completion
        handler or through the session’s delegate. The API of NSURLSession was
        designed with flexibility in mind as you’ll notice a bit later in this tutorial.

        正如你所想的,異步性是NSURLSession的關鍵概念。使用 NSURLSession 的 API 返回數據可以通過調用完成處理程序塊或者通過會話的委托。NSURLSession 的 API 在設計的時候充分考慮瞭靈活性,關於這一點,你將會在本教程的後面體會到。

        Meet the Family

        As I mentioned earlier, NSURLSession is
        both a technology and a class that you’ll be working with. The NSURLSession API
        houses a number of classes, but NSURLSession is the key
        component sending requests and receiving responses. The configuration of the session object, however, is handled by an instance of the NSURLSessionConfigurationclass.
        The NSURLSessionTask class and its three concrete subclasses
        are the workers and are always used in conjunction with a session as it is the session that creates the task objects.

        正如我之前提到的,NSURLSession 即是指一種新的網絡開發技術,同時也是一個類。NSURLSession 的 API 包括若幹個類,但是 NSURLSession 是關鍵的部件用於發送請求和接收響應,而會話對象的配置是由 NSURLSessionConfiguration 實例進行處理。NSURLSessionTask 和其三個具體的子類總是和會話對象關聯在一起,因為會話對象創建瞭任務對象。


        Both NSURLSession and NSURLConnection rely
        heavily on the delegation pattern. TheNSURLSessionDelegate protocol
        declares a handful of delegate methods for handling events at the session-level. In addition, the NSURLSessionTask class
        and subclasses each declare a delegate protocol for handling task-level events.

        NSURLSession 和 NSURLConnection 都采用瞭委托代理模式。NSURLSessionDelegate 委托協議聲明瞭少量的委托方法用於處理會話事件。除此之外,NSURLSessionTask 和其子類都各自有一個委托協議用於處理任務事件。

        Old Friends

        The NSURLSession API builds on top of
        classes that you’re already familiar with, such asNSURL, NSURLRequest,
        and NSURLResponse.

        NSURLSession 的 API 的使用涉及到你曾熟悉的類,例如:NSURL,NSURLRequest 和 NSURLResponse。

        What are the differences?

        How does NSURLSession differ from NSURLConnection?
        This is an important question, because NSURLConnection is
        not being deprecated by Apple. You can still useNSURLConnection in
        your projects. Why should you use NSURLSession?

        那麼 NSURLSession 和 NSURLConnection 有什麼不同呢?這是一個關鍵的問題,因為 NSURLConnection 還未被 Apple 棄用。你仍可以是項目中使用 NSURLConnection。那何時使用 NSURLSession 呢?

        The first thing to understand is that the NSURLSession instance
        is the object that manages the request and response. This is similar to how NSURLConnection works,
        but the key difference is that the configuration of the request is handled by the session object, which is a long lived object. This is done through the NSURLSessionConfigurationclass.
        Not only does it provide the NSURLSession API fine-grained
        configuration through the NSURLSessionConfiguration class,
        it encourages the separation of data (request body) from metadata. The NSURLSessionDownloadTask illustrates
        this well by directly writing the response to the file system.

        首先要瞭解到,NSURLSession 實例對象是管理請求和響應的,這和 NSURLConnection 相似,但區別是管理請求配置的是一個長壽命對象,由 NSURLSessionConfiguration 類對象完成。通過 NSURLSessionConfiguration 類不僅提供瞭細粒度配置的 API 管理 NSURLSession 會話對象,而且將數據(請求主體)從元數據(metadata)中分離開來。NSURLSessionDownloadTask 通過直接將響應數據寫入文件系統就很好的說明瞭這一點。

        Authentication is easier and handled more elegantly by NSURLSession.
        TheNSURLSession API handles authentication on a connection
        basis instead of on a request basis, like NSURLConnection does.
        The NSURLSession API also makes it more convenient to provide
        HTTP options and each session can have a separate storage container depending on how you configure the session.

        通過 NSURLSession 可以更容易的實現身份認證。NSURLSession 的 API 處理身份認證是在連接的基礎上進行而非在請求的基礎上進行,和 NSURLConnection 的處理方式是一樣的。NSURLSession 的 API 提供瞭方便的 HTTP

        In the introduction, I told you that NSURLSession provides
        a modern interface, which integrates gracefully with iOS 7. One example of this integration is NSURLSession‘s
        out-of-process uploads and downloads. NSURLSession is optimized
        to preserve battery life, supports pausing, canceling, and resuming of tasks as well as UIKit’s multitasking API. What is not to love about NSURLSession?

        在引言中,我告訴你 NSURLSession 提供瞭一個現代化的接口,它與iOS 7優雅的集成整合。這種集成整合體現的一個例子是 NSURLSession 的上傳下載是在進程外進行。 NSURLSession 也進行瞭優化以保持電池的使用壽命,提供暫停,取消和恢復任務,還支持 UIKit 的多線程處理的 API 。還有什麼理由不愛 NSURLSession 呢?

        Getting Your Feet Wet

        Step 1: Project Setup

        A new API is best learned by practice so it’s time to fire up Xcode and get our feet wet. Launch Xcode 5, create a new project by selecting New > Project… from the Filemenu, and select the Single
        View Application template from the list of iOS application templates.

        實踐出真知。在 Xcode 5 中創建一個單視圖項目。

        Create a new project in Xcode 5.

        Give your project a name, tell Xcode where you’d like to save it, and hit Create. There’s no need to put the project under source control.

        Configure the project.

        Step 2: Create a Session Object

        When working with NSURLSession, it is
        important to understand that the session object, an instance of NSURLSession,
        is the star player. It handles the requests and responses, configures the requests, manages session storage and state, etc. Creating a session can be done several ways. The quickest way to get started is to use NSURLSession‘ssharedSession class
        method as shown below.

        在使用 NSURLSession 的時候,重點是理解會話對象(它可是主角),NSURLSession 的實例。它處理請求和響應,配置請求,管理會話的存儲和狀態,等等。創建會話對象有多種方式,最快的是使用 NSURLSession
        的 sharedSession 類方法。

        - (void)viewDidLoad {
            [super viewDidLoad];
            NSURLSession *session = [NSURLSession sharedSession];

        Create a session object in the view controller’s viewDidLoad method
        as shown above. The session object we created is fine for
        our example, but you probably want a bit more flexibility in most cases. The session object
        we just created uses the globalNSURLCache, NSHTTPCookieStorage,
        and NSURLCredentialStorage. This means that it works pretty
        similar to a default implementation of NSURLConnection.

        在視圖控制器的 viewDidLoad 方法創建瞭 session 會話對象,這個會話對象已經適合我們例子的需要瞭,但是在大多數情況下可能需要多一點的靈活性。剛剛我們創建的 session 對象使用全局的 NSURLCache,NSHTTPCookieStorage 和 NSURLCredentialStorage。這樣 session 的工作狀態就和 NSURLConnection 的默認實現類似。

        Step 3: Create a Data Task

        To put the session object to use, let’s
        query the iTunes Store
        Search API and search for software made by Apple. The iTunes Store Search API is easy to use and requires no authentication, which makes it ideal for our example.

        為瞭測試 session 對象的使用,我們使用 Apple 提供的數據查詢接口: iTunes
        Store Search API ,這個開發接口易於使用且不需要身份驗證,適合本例子。

        To query the search API, we need to send a request to and
        pass a few parameters. As we saw earlier, when using the NSURLSession API,
        a request is represented by a task. To query the search API, all we need is a data task, an instance of the NSURLSessionDataTask class.
        Take a look at the updated viewDidLoad implementation shown

        我們發送請求並傳遞一些請求參數。正如我們之前所見,一個任務代表一個請求。使用查詢接口請求數據,我們需要一個 Data task,NSURLSessionDataTask 的實例。修改 viewDidLoad 方法如下:

        - (void)viewDidLoad {
            [super viewDidLoad];
            NSURLSession *session = [NSURLSession sharedSession];
            NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@""]completionHandler:^(NSData *data,NSURLResponse *response,NSError *error) {
                NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
                NSLog(@"%@", json);

        There are a number of methods available to create a task, but the key concept to understand is that the session object
        does the actual creation and configuration of the task. In this example, we invoke dataTaskWithURL:completionHandler: and
        pass it an instance of NSURL as well as a completion handler.
        The completion handler accepts three arguments, the raw data of the response (NSData),
        the response object(NSURLResponse),
        and an error object (NSError). If
        the request is successful, the error object is nil. Because
        we know the request returns a JSON response, we create a Foundation object from the data object
        that we’ve received and log the output to the console.

        有許多方法來創建一個任務,但是關鍵概念的理解是 session 對象創建且配置瞭任務。在這個例子中,我們調用 dataTaskWithURL:completionHandler: 方法,並傳遞
        NSURL 對象和一個完成程序代碼塊作為參數。完成程序代碼塊有三個參數:請求響應的原始數據(NSData),響應對象(NSURLResponse)和一個錯誤對象(NSError)。如果請求成功,錯誤對象就為 nil。因為我們知道請求返回的是JSON數據作為響應,我們用 data 對象接收響應,並在終端中輸出。

        It is important to understand that the error object passed
        to the completion handler is only populated, not nil, if
        the request failed or encountered an error. In other words, if the request returned a 404 response,
        the request did succeed as far as the sessions is concerned. The error object
        will then be nil. This is an important concept to grasp
        when working with NSURLSession and NSURLConnection for
        that matter.

        Build the project and run the application in the iOS Simulator or on a physical device, and inspect Xcode’s Console. Nothing is printed to the console. What went wrong? As I mentioned earlier, the NSURLSession API
        supports pausing, canceling, and resuming of tasks or requests. This behavior is similar to that of NSOperation and
        it may remind you of the AFNetworking library. A task has a state property
        that indicates whether the task is running (NSURLSessionTaskStateRunning), suspended(NSURLSessionTaskStateSuspended), canceling (NSURLSessionTaskStateCanceling),
        or completed (NSURLSessionTaskStateCompleted).
        When a session object creates a task, the task starts its life in the suspended state. To start the task, we need to tell it toresume by
        calling resume on the task. Update the viewDidLoad method
        as shown below, run the application one more time, and inspect the output in the console. Mission accomplished.

        編譯運行程序在模擬器或者真機,然後觀察 Xcode 的終端,會發現沒有東西輸出。是否出錯瞭呢?正如我之前所說,NSURLSession 的 API 支持暫停,取消和恢復請求任務。這個和 NSOperation 很相似。任務有一個 state 狀態屬性指示一個任務是否正在運行 (NSURLSessionTaskStateRunning),是否停止(NSURLSessionTaskStateSuspended),是否取消(NSURLSessionTaskStateCanceling)或者是否完成(NSURLSessionTaskStateCompleted)。當一個會話對象創建瞭一個任務,這個任務初始時是處於停止狀態。為瞭啟動任務,我們需要調用
        resume 方法去啟動任務執行。修改 viewDidLoad 方法如下,重新運行,觀察終端中的輸出。

        - (void)viewDidLoad {
            [super viewDidLoad];
            NSURLSession *session = [NSURLSession sharedSession];
            NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@""]completionHandler:^(NSData *data,NSURLResponse *response,NSError *error) {
                NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
                NSLog(@"%@", json);

        Downloading a Remote Resource

        In the previous example, we made use of a completion handler to process the response we received from the request. It’s also possible to achieve the same result by implementing the task delegate protocol(s). Let’s see what it takes to download an image by leveraging NSURLSession and
        the NSURLSessionDownloadTask.

        在前一個例子中,我們使用完成程序代碼塊去處理請求接收到的響應。也可以通過委托協議來實現相同過程。讓我們看看借助 NSURLSession 和 NSURLSessionDownloadTask 完成下載圖像。

        Step 1: Create the User Interface

        Open MTViewController.h and create two
        outlets as shown below. We’ll use the first outlet, an instance of UIImageView,
        to display the downloaded image to the user. The second outlet, an instance of UIProgressView,
        will show the progress of the download task.

        打開 MTViewController.h 然後創建如下的兩個 outlet 。我們使用第一個 outlet,UIImageView 的實例,去顯示下載的圖像;第二個 outlet ,UIProgressView 的實例,去顯示下載任務的進度。

        @interface MTViewController: UIViewController
        @property(weak,nonatomic)IBOutlet UIImageView *imageView;
        @property(weak,nonatomic)IBOutlet UIProgressView *progressView;

        Open the project’s main storyboard (Main.storyboard), drag a UIImageView instance
        to the view controller’s view, and connect the view controller’s outlet that we just created in the view controller’s header file. Repeat this process for the progress view.

        打開項目的 storyboard,拖動 UIImageView 到視圖中,然後和視圖控制器中的 outlet 進行連接對應。重復這個過程完成添加進度條。

        Step 2: Create a Download Task

        In this example, we won’t make use of the sharedSession class
        method, because we need to configure the session object
        that we’ll use to make the request. Update the implementation of viewDidLoad as
        shown below.

        在這個例子中,我們不使用 sharedSession 類方法創建會話對象,因為需要根據請求對會話對象進行配置。在viewDidLoad 方法中:

        - (void)viewDidLoad {
            [super viewDidLoad];
            NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
            NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
            NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:[NSURL URLWithString:@""]];
            [downloadTask resume];

        To prevent any compiler warning from popping up, make sure to conform theMTViewController class
        to the NSURLSessionDelegate and NSURLSessionDownloadDelegateprotocols
        as shown below.

        為瞭避免出現編譯錯誤,確保 MTViewController 類遵循
        NSURLSessionDownloadDelegate 協議。

        #import "MTViewController.h"
        @interface MTViewController() 

        In viewDidLoad, we create an instance of the NSURLSessionConfiguration class
        by invoking the defaultSessionConfiguration class method.
        As stated in the documentation, by using the default session configuration the session will behave much like an instance of NSURLConnection in
        its default configuration, which is fine for our example.

        在 viewDidLoad 方法中,我們通過調用
        defaultSessionConfiguration 類方法創建
        NSURLSessionConfiguration 類的實例。使用默認的會話配置將會使會話的行為和 NSURLConnection 實例的默認配置類似。

        In this example, we create a NSURLSession instance by invoking
        thesessionWithConfiguration:delegate:delegateQueue: class
        method and pass thesessionConfiguration object we created
        a moment ago. We set the view controller as the session delegate and pass nil as
        the third argument. You can ignore the third argument for now. The main difference with the previous example is that we set thesession‘s
        delegate to the view controller.

        在這個例子中,我們創建創建 NSURLSession 實例是通過調用
        sessionWithConfiguration:delegate:delegateQueue: 類方法,然後傳入 sessionConfiguration 對象作為參數進行創建。我們設置視圖控制器為會話的委托對象,然後傳遞 nil 給第三個參數。你可以暫時忽略第三個參數的含義。這和之前例子最主要的不同是我們設置 session 的委托對象為視圖控制器。

        To download the image, we need to create a download task. We do this by callingdownloadTaskWithURL: on
        the session object, passing an instance of NSURL,
        and callingresume on the download task. We could have made
        use of a completion handler like we did earlier, but I want to show you the possibilities of using a delegate instead.

        為瞭下載圖像,我們要創建一個 Download 任務。我們在 session 會話對象上調用
        downloadTaskWithURL: 方法,傳入一個 NSURL 實例,在調用
        resume 方法去開始下載任務。我們這裡可以使用完成程序代碼塊處理接收請求響應,但是我這裡想要使用委托方法進行實現。

        Step 3: Implement the Delegate Protocol

        To make all this work, we need to implement the three delegate methods of theNSURLSessionDownloadDelegate protocol,URLSession:downloadTask:didFinishDownloadingToURL:,

        andURLSession:downloadTask:downloadTask didWriteData:totalBytesWritten:totalBytesExpectedToWrite:.
        The implementation of each method is quite easy. It’s important to note that we need update the user interface on the main thread using GCD (Grand Central Dispatch). By passing nil as
        the third argument of sessionWithConfiguration:delegate:delegateQueue:,
        the operating system created a background queue for us. This is fine, but it also means that we need to be aware that the delegate methods are invoked on a background thread instead of the main thread. Build the project and run the application to see the result
        of our hard work.

        NSURLSessionDownloadDelegate 委托協議的如下三個方法:URLSession:downloadTask:didFinishDownloadingToURL: ,URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:

        didWriteData:totalBytesWritten:totalBytesExpectedToWrite: 。每一個委托方法的實現都比較簡單。重點需要註意的是要使用 GCD(Grand Central Dispatch)
        sessionWithConfiguration:delegate:delegateQueue: 方法中傳入 nil 作為其第三個參數,操作系統自動為我們創建一個後臺隊列。在本例中是適用的,同時我們也要意識到這些委托方法是在後臺線程中進行調用執行的,而非在主程序中。編譯運行項目程序,觀察結果。

        - (void)URLSession:(NSURLSession*)session downloadTask:(NSURLSessionDownloadTask*)downloadTask didFinishDownloadingToURL:(NSURL*)location {
            NSData *data = [NSData dataWithContentsOfURL:location];
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.progressView setHidden:YES];
                [self.imageViewsetImage:[UIImage imageWithData:data]];
        - (void)URLSession:(NSURLSession*)session downloadTask:(NSURLSessionDownloadTask*)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
        - (void)URLSession:(NSURLSession*)session downloadTask:(NSURLSessionDownloadTask*)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
            floatprogress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.progressView setProgress:progress];


        With these two examples, you should have a basic understanding of the fundamentals of the NSURLSession API,
        how it compares to NSURLConnection, and what its advantages
        are. In the next part of this series, we will look at more advanced features ofNSURLSession.

        有瞭這兩個例子,你應該對 NSURLSession 的基礎知識,它和 NSURLConnection 的比較,它的優勢有瞭一定的瞭解。在下一篇教程中,我們將深入瞭解 NSURLSession 的其它先進特性。