詳細解析幾個和網絡請求有關的類(六) —— 使用NSURLSession(二)

版本記錄

版本號 時間
V1.0 2018.03.10

前言

我們做APP發(fā)起網絡請求,一般都是使用框架,這些框架的底層也都是蘋果的API,接下來幾篇就一起來看一下和網絡有關的幾個類。感興趣的可以看上面幾篇文章。
1. 詳細解析幾個和網絡請求有關的類 (一) —— NSURLSession
2. 詳細解析幾個和網絡請求有關的類(二) —— NSURLRequest和NSMutableURLRequest
3. 詳細解析幾個和網絡請求有關的類(三) —— NSURLConnection
4. 詳細解析幾個和網絡請求有關的類(四) —— NSURLSession和NSURLConnection的區(qū)別
5. 詳細解析幾個和網絡請求有關的類(五) —— 關于NSURL加載系統(tǒng)(一)

回顧

上一篇主要介紹了關于URL系統(tǒng)這個蘋果框架的概覽和描述,包括URL加載系統(tǒng)的各個組成部分。本篇主要介紹一個關于NSURLSession的使用。


Using NSURLSession - 使用NSURLSession

NSURLSession類和相關的類提供了一個通過HTTP下載內容的API。此API提供了一組豐富的代理方法來支持身份驗證,并使您的應用能夠在應用未運行時執(zhí)行后臺下載,或者在iOS中暫停應用時執(zhí)行后臺下載。

要使用NSURLSession API,您的應用程序會創(chuàng)建一系列會話,每個會話都會協(xié)調一組相關的數(shù)據(jù)傳輸任務。例如,如果您正在編寫Web瀏覽器,則您的應用程序可能會為每個選項卡或窗口創(chuàng)建一個會話。在每個會話中,您的應用程序會添加一系列任務,其中每個任務均表示對特定網址的請求(如果原始網址返回HTTP重定向,則表示任何后續(xù)網址)。

像大多數(shù)網絡API一樣,NSURLSession API是高度異步的。如果您使用默認的系統(tǒng)提供的代理,則必須提供完成處理程序塊,以便在傳輸成功完成或發(fā)生錯誤時將數(shù)據(jù)返回到您的應用程序。或者,如果您提供自己的自定義委托對象,那么任務對象會調用這些委托的方法并傳遞從服務器接收的數(shù)據(jù)(或者,在傳輸完成時用于文件下載)。

注意:完成回調主要是作為使用自定義代理的替代方案。如果使用接受完成回調的方法創(chuàng)建任務,則不會調用響應和數(shù)據(jù)傳遞的代理方法。

NSURLSession API除了將這些信息傳遞給委代理之外,還提供狀態(tài)和進度屬性。它支持取消,重新啟動(恢復)和暫停任務,并且可以在停止的地方恢復已暫停,取消或失敗的下載。


Understanding URL Session Concepts - 理解URL Session的概念

會話中任務的行為取決于三件事情:會話類型(由用于創(chuàng)建會話的配置對象的類型決定),任務類型以及創(chuàng)建任務時應用程序是否處于前臺。

1. Types of Sessions - 會話類型

NSURLSession API支持三種類型的會話,這取決于用于創(chuàng)建會話的配置對象的類型:

  • 默認會話Default sessions的行為與其他Foundation下載URL的方法類似。他們使用永久性的基于磁盤的緩存并將憑證存儲在用戶的鑰匙串中。
  • 臨時會話Ephemeral sessions不會將任何數(shù)據(jù)存儲到磁盤;所有的緩存,憑證存儲等都保存在RAM中并與會話綁定。因此,當您的應用程序使會話失效時,它們會自動清除。
  • 后臺會話Background sessions與默認會話類似,不同之處在于單獨的進程處理所有數(shù)據(jù)傳輸。后臺會話有一些額外的限制,如Background Transfer Considerations中所述。

2. Types of Tasks - 任務類型

在會話中,NSURLSession類支持三種類型的會話:數(shù)據(jù)任務,下載任務和上載任務。

  • 數(shù)據(jù)任務使用NSData對象發(fā)送和接收數(shù)據(jù)。數(shù)據(jù)任務適用于從應用程序到服務器的簡短交互式請求。數(shù)據(jù)任務可以在接收每一條數(shù)據(jù)后一次將數(shù)據(jù)返回給您的應用程序,或者通過完成處理程序一次全部返回數(shù)據(jù)。
  • 下載任務以文件形式檢索數(shù)據(jù),并在應用程序未運行時支持后臺下載。
  • 上傳任務以文件的形式發(fā)送數(shù)據(jù),并在應用程序未運行時支持后臺上傳。

3. Background Transfer Considerations - 后臺傳輸注意事項

當您的應用程序被暫停的時候,NSURLSession類支持后臺傳輸。后臺傳輸僅由使用后臺會話配置對象(通過調用backgroundSessionConfiguration:)返回的會話創(chuàng)建)。

對于后臺會話,由于實際傳輸是由單獨的進程執(zhí)行的,因為重新啟動應用程序的過程相對昂貴,所以有幾項功能不可用,導致以下限制:

  • session必須提供事件傳遞的代理。 (對于上傳和下載,代理的行為與進行中的傳輸相同。)
  • 只支持HTTP和HTTPS協(xié)議(沒有自定義協(xié)議)。
  • 重定向總是被遵循。
  • 只支持從文件上傳任務(在程序退出后,從數(shù)據(jù)對象上傳數(shù)據(jù)或流將失?。?。
  • 如果在應用程序處于后臺時啟動后臺傳輸,則配置對象的discretionary屬性將被視為是true的。

注意:在iOS 8和OS X 10.10之前,后臺會話不支持數(shù)據(jù)任務。

iOS和OS X之間重新啟動時應用的行為方式略有不同。

在iOS中,當后臺傳輸完成或需要證書時,如果您的應用程序不再運行,iOS會自動在后臺重新啟動您的應用程序,并在應用程序的UIApplicationDelegate對象中調用application:handleEventsForBackgroundURLSession:completionHandler:方法。此調用提供了導致您的應用程序啟動的會話的標識符。您的應用程序應該存儲該完成處理程序,使用相同的標識符創(chuàng)建后臺配置對象,并使用該配置對象創(chuàng)建會話。新會話將自動與正在進行的背景活動重新關聯(lián)。稍后,當會話完成最后一個后臺下載任務時,它會向會話委托發(fā)送URLSessionDidFinishEventsForBackgroundURLSession:消息。在該代理方法中,調用主線程上先前存儲的完成處理程序,以便操作系統(tǒng)知道再次掛起應用程序是安全的。

在iOS和OS X中,當用戶重新運行應用程序時,應用程序應立即創(chuàng)建具有與應用程序上次運行時的未完成任務的任何會話具有相同標識符的后臺配置對象,然后為每個配置對象創(chuàng)建一個會話。這些新會話同樣會自動與正在進行的背景活動重新關聯(lián)。

注意:每個標識符必須創(chuàng)建一個會話(在創(chuàng)建配置對象時指定)。 共享相同標識符的多個會話的行為未定義。

如果任何任務在您的應用程序被暫停時完成,則代理的URLSession:downloadTask:didFinishDownloadingToURL: 方法將隨任務和與其關聯(lián)的新下載文件的URL一起調用。

同樣,如果任何任務需要證書,則NSURLSession對象會根據(jù)需要調用委托的URLSession:task:didReceiveChallenge:completionHandler:方法或 URLSession:didReceiveChallenge:completionHandler:方法。

網絡錯誤后,URL加載系統(tǒng)會自動重試后臺會話中的上傳和下載任務。 沒有必要使用reachability API來確定何時重試失敗的任務。

有關如何使用NSURLSession進行后臺傳輸?shù)氖纠垍㈤?em>Simple Background Transfer

4. Life Cycle and Delegate Interaction - 生命周期和代理交互

根據(jù)您對NSURLSession類所做的工作,可能有助于全面了解會話生命周期,包括會話與其代理進行交互的方式,代理調用的順序,服務器返回重定向時發(fā)生的情況, 當您的應用恢復失敗的下載時會發(fā)生什么,等等。

有關URL會話生命周期的完整說明,請閱讀Life Cycle of a URL Session。

5. NSCopying Behavior - NSCopying 行為

會話和任務對象符合NSCopying協(xié)議,如下所示:

  • 當您的應用程序復制會話或任務對象時,會返回相同的對象。
  • 當你的應用程序復制一個配置對象時,你會得到一個你可以獨立修改的新副本。

Sample Delegate Class Interface - 代理類接口

以下任務部分中的代碼片段基于Listing 1-1中顯示的類接口。

// Listing 1-1  Sample delegate class interface
 
@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
import Foundation
 
typealias CompletionHandler = () -> Void
 
class MySessionDelegate : NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDataDelegate, URLSessionDownloadDelegate, URLSessionStreamDelegate {
    var completionHandlers: [String: CompletionHandler] = [:]
}

Creating and Configuring a Session - 創(chuàng)建和配置Session

NSURLSession API提供了廣泛的配置選項:

  • 專用于單個會話的專用存儲支持緩存,Cookie,憑證和協(xié)議
  • 身份驗證,綁定到特定請求(任務)或一組請求(會話)
  • 通過URL上傳和下載文件,鼓勵將數(shù)據(jù)(文件內容)與元數(shù)據(jù)(URL和設置)分開
  • 配置每臺主機的最大連接數(shù)
  • 如果無法在一定時間內下載整個資源,則會觸發(fā)資源超時
  • 最小和最大TLS版本支持
  • 自定義代理字典
  • 控制cookie策略
  • 控制HTTP流水線行為

由于大多數(shù)設置都包含在單獨的配置對象中,因此您可以重復使用常用的設置。在實例化會話對象時,您需要指定以下內容:

  • 管理該會話及其中任務的行為的配置對象
  • 可選地,委托對象處理接收到的數(shù)據(jù)并處理特定于會話及其中的任務的其他事件,如服務器身份驗證,確定資源下載請求是否應轉換為下載等等。

如果您不提供委托,則NSURLSession對象使用系統(tǒng)提供的委托。通過這種方式,您可以輕松使用NSURLSession代替在NSURLSession上使用sendAsynchronousRequest:queue:completionHandler:方法的現(xiàn)有代碼。

注意:如果您的應用需要執(zhí)行后臺傳輸,則必須提供自定義代理。

在實例化會話對象后,不能在不創(chuàng)建新會話的情況下更改配置或代理。

Listing 1-2顯示了如何創(chuàng)建正常,臨時和后臺會話的示例。

// 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];
// Creating session configurations
let defaultConfiguration = URLSessionConfiguration.default()
let ephemeralConfiguration = URLSessionConfiguration.ephemeral()
let backgroundConfiguration = URLSessionConfiguration.backgroundSessionConfiguration(withIdentifier: "com.myapp.networking.background")
 
// Configuring caching behavior for the default session
let cachesDirectoryURL = FileManager.default().urlsForDirectory(.cachesDirectory, inDomains: .userDomainMask).first!
let cacheURL = try! cachesDirectoryURL.appendingPathComponent("MyCache")
var diskPath = cacheURL.path
 
/* Note:
 iOS requires the cache path to be
 a path relative to the ~/Library/Caches directory,
 but OS X expects an absolute path.
 */
#if os(OSX)
diskPath = cacheURL.absoluteString
#endif
 
let cache = URLCache(memoryCapacity:16384, diskCapacity: 268435456, diskPath: diskPath)
defaultConfiguration.urlCache = cache
defaultConfiguration.requestCachePolicy = .useProtocolCachePolicy
// Creating sessions
let delegate = MySessionDelegate()
let operationQueue = OperationQueue.main()
 
let defaultSession = URLSession(configuration: defaultConfiguration, delegate: delegate, delegateQueue: operationQueue)
let ephemeralSession = URLSession(configuration: ephemeralConfiguration, delegate: delegate, delegateQueue: operationQueue)
let backgroundSession = URLSession(configuration: backgroundConfiguration, delegate: delegate, delegateQueue: operationQueue)

除后臺配置外,您可以重新使用會話配置對象來創(chuàng)建其他會話。 (您不能重復使用后臺會話配置,因為共享相同標識符的兩個后臺會話對象的行為未定義。)

您也可以隨時安全地修改配置對象。 在創(chuàng)建會話時,會話會在配置對象上執(zhí)行深層復制,因此修改只會影響新會話,而不會影響現(xiàn)有會話。 例如,您可以為僅在您使用Wi-Fi連接時才能檢索的內容創(chuàng)建第二個會話,如Listing 1-3所示。

ephemeralConfiguration.allowsCellularAccess = NO;
NSURLSession *ephemeralSessionWiFiOnly = [NSURLSession sessionWithConfiguration:ephemeralConfiguration delegate:delegate delegateQueue:operationQueue];
ephemeralConfiguration.allowsCellularAccess = false
let ephemeralSessionWiFiOnly = URLSession(configuration: ephemeralConfiguration, delegate: delegate, operationQueue: operationQueue)

Fetching Resources Using System-Provided Delegates - 使用系統(tǒng)提供的代理獲取響應

使用NSURLSession最直接的方法是使用系統(tǒng)提供的代理來請求資源。 使用這種方法,您只需在應用程序中提供兩段代碼:

  • 代碼根據(jù)該對象創(chuàng)建配置對象和會話
  • 完成處理程序例程,用于在數(shù)據(jù)完全接收后執(zhí)行某些操作

使用系統(tǒng)提供的委托,每個請求只需一行代碼即可獲取特定的URL。 Listing 1-4顯示了這個簡化形式的一個例子。

注意:系統(tǒng)提供的代理僅提供有限的網絡行為定制。 如果您的應用程序有超出基本URL抓取的特殊需求,例如自定義身份驗證或后臺下載,則此技術不適用。 有關您必須實施完整代理的情況的完整列表,請參閱Life Cycle of a URL Session

// Listing 1-4  Requesting a resource using system-provided delegates

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];
let sessionWithoutADelegate = URLSession(configuration: defaultConfiguration)
if let url = URL(string: "https://www.example.com/") {
    (sessionWithoutADelegate.dataTask(with: url) { (data, response, error) in
        if let error = error {
            print("Error: \(error)")
        } else if let response = response,
            let data = data,
            let string = String(data: data, encoding: .utf8) {
            print("Response: \(response)")
            print("DATA:\n\(string)\nEND DATA\n")
        }
    }).resume()
}

Fetching Data Using a Custom Delegate - 使用自定義代理獲取數(shù)據(jù)

如果您使用自定義代理來檢索數(shù)據(jù),那么代理必須至少實現(xiàn)以下方法:

如果您的應用需要在其URLSession:dataTask:didReceiveData:方法返回后使用數(shù)據(jù),則您的代碼負責以某種方式存儲數(shù)據(jù)。

例如,Web瀏覽器可能需要在數(shù)據(jù)到達時隨同它之前收到的任何數(shù)據(jù)一起呈現(xiàn)數(shù)據(jù)。 為此,它可能會使用將任務對象映射到NSMutableData對象以存儲結果的字典,然后使用該對象上的appendData:方法追加新接收的數(shù)據(jù)。

Listing 1-5顯示了如何創(chuàng)建和啟動數(shù)據(jù)任務

NSURL *url = [NSURL URLWithString: @"https://www.example.com/"];
NSURLSessionDataTask *dataTask = [defaultSession dataTaskWithURL:url];
[dataTask resume];
if let url = URL(string: "https://www.example.com/") {
    let dataTask = defaultSession.dataTask(with: url)
    dataTask.resume()
}

Downloading Files - 下載文件

在高層次上,下載文件與檢索數(shù)據(jù)類似。 你的應用應該實現(xiàn)下面的委托方法:

重要提示:在此方法返回之前,它必須打開文件進行讀取或將其移至永久位置。 當此方法返回時,如果臨時文件仍然存在于其原始位置,則該臨時文件將被刪除。

如果您在后臺會話中安排下載,則當您的應用程序未運行時,下載將繼續(xù)。如果您在標準或臨時會話中安排下載,則必須在重新啟動應用程序時重新開始下載。

在從服務器傳輸?shù)倪^程中,如果用戶通知您的應用程序暫停下載,則您的應用程序可以通過調用cancelByProducingResumeData:方法取消該任務。稍后,您的應用可以將返回的重續(xù)數(shù)據(jù)傳遞給downloadTaskWithResumeData:downloadTaskWithResumeData:completionHandler:方法以創(chuàng)建一個新的下載任務,以繼續(xù)下載。

如果傳輸失敗,則調用返回NSError對象的代理方法URLSession:task:didCompleteWithError:方法。如果任務可恢復,則該對象的userInfo字典包含NSURLSessionDownloadTaskResumeData鍵的值;您的應用可以將返回的續(xù)傳數(shù)據(jù)傳遞給downloadTaskWithResumeData:downloadTaskWithResumeData:completionHandler:方法以創(chuàng)建一個新的下載任務,以重試下載。

Listing 1-6提供了一個下載適度大文件的例子。Listing1-7提供了下載任務代理方法的示例。

// Listing 1-6  Download task example

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];
if let url = URL(string: "https://www.example.com/") {
    let dataTask = defaultSession.dataTask(with: url)
    dataTask.resume()
}
// Listing 1-7  Delegate methods for download tasks

- (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]) {
        // ...
    }
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
    print("Session \(session) download task \(downloadTask) wrote an additional \(bytesWritten) bytes (total \(totalBytesWritten) bytes) out of an expected \(totalBytesExpectedToWrite) bytes.\n")
}
 
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
    print("Session \(session) download task \(downloadTask) resumed at offset \(fileOffset) bytes out of an expected \(expectedTotalBytes) bytes.\n")
}
 
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
    print("Session \(session) download task \(downloadTask) finished downloading to URL \(location)\n")
    
    // Perform the completion handler for the current session
    self.completionHandlers[session.configuration.identifier]()
    
    // Open the downloaded file for reading
    if let fileHandle = try? FileHandle(forReadingFrom: location) {
        // ...
    }
    
    // Move the file to a new URL
    let fileManager = FileManager.default()
    if let cacheDirectory = fileManager.urlsForDirectory(.cachesDirectory, inDomains: .userDomainMask).first {
        do {
            try fileManager.moveItem(at: location, to: cacheDirectory)
        } catch {
            // ...
        }
    }
}

Uploading Body Content - 上傳主體內容

您的應用程序可以通過三種方式為HTTP POST請求提供請求正文內容:作為NSData對象,文件file或流stream。一般來說,你的應用程序應該:

  • 如果您的應用程序已經擁有內存中的數(shù)據(jù)并且沒有理由處置它,請使用NSData對象。
  • 如果正在上傳的內容作為磁盤上的文件存在,如果您正在進行后臺傳輸,或者如果您的應用程序將其寫入磁盤以便釋放與該數(shù)據(jù)關聯(lián)的內存,則可以使用該文件。
  • 如果您通過網絡接收數(shù)據(jù),請使用流。

無論您選擇哪種風格,如果您的應用程序提供自定義會話委托,則該委托應實現(xiàn)URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:方法以獲取上載進度信息。

此外,如果您的應用程序使用流提供請求主體,則它必須提供實現(xiàn)URLSession:task:needNewBodyStream:方法的自定義會話委托,在 Uploading Body Content Using a Stream中有更詳細的描述。

1. Uploading Body Content Using an NSData Object - 使用NSData對象上傳體內容

要使用NSData對象上傳主體內容,應用程序會調用uploadTaskWithRequest:fromData:uploadTaskWithRequest:fromData:completionHandler:方法來創(chuàng)建上載任務,并通過fromData參數(shù)提供請求主體數(shù)據(jù)。

會話對象根據(jù)數(shù)據(jù)對象的大小計算Content-Length頭。

您的應用必須提供服務器可能需要的任何附加頭信息(例如內容類型content type)作為URL請求對象的一部分。

// Listing 1-8  Uploading task from data example

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];
let textFileURL = URL(fileURLWithPath: "/path/to/file.txt")
if let data = try? Data(contentsOf: textFileURL) {
    if let url = URL(string: "https://www.example.com/") {
        var mutableRequest = MutableURLRequest(url: url)
        mutableRequest.httpMethod = "POST"
        mutableRequest.setValue("\(data.count)", forHTTPHeaderField: "Content-Length")
        mutableRequest.setValue("text/plain", forHTTPHeaderField: "Content-Type")
        
        let uploadTask = defaultSession.uploadTask(with: mutableRequest, from: data)
        uploadTask.resume()
    }
}

2. Uploading Body Content Using a File - 使用文件上傳體內容

要從文件上傳主體內容,應用程序會調用uploadTaskWithRequest:fromFile:uploadTaskWithRequest:fromFile:completionHandler:方法來創(chuàng)建上載任務,并提供任務讀取主體內容的文件URL。

會話對象根據(jù)數(shù)據(jù)對象的大小計算Content-Length頭。 如果您的應用沒有為Content-Type標頭提供值,則會話也會提供一個值。

您的應用程序可以提供服務器可能需要的任何其他標頭信息,作為URL請求對象的一部分。

// Listing 1-9  Uploading task from streamed request example

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];
let textFileURL = URL(fileURLWithPath: "/path/to/file.txt")
if let url = URL(string: "https://www.example.com/") {
    var mutableRequest = MutableURLRequest(url: url)
    mutableRequest.httpMethod = "POST"
    mutableRequest.httpBodyStream = InputStream(fileAtPath: textFileURL.path!)
    mutableRequest.setValue("\(data.count)", forHTTPHeaderField: "Content-Length")
    mutableRequest.setValue("text/plain", forHTTPHeaderField: "Content-Type")
    
    let uploadTask = defaultSession.uploadTask(with: mutableRequest, from: textFileURL)
    uploadTask.resume()
}

3. Uploading Body Content Using a Stream - 使用流上傳體內容

要使用流上傳正文內容,您的應用程序會調用uploadTaskWithStreamedRequest:方法來創(chuàng)建上傳任務。您的應用程序提供了一個請求對象,其中有一個關聯(lián)的流,任務從中讀取主體內容。

您的應用必須提供服務器可能需要的任何附加頭信息 - 例如內容類型和長度 - 作為URL請求對象的一部分。

另外,因為會話不一定會回滾提供的流以重新讀取數(shù)據(jù),所以如果會話必須重試請求(例如,如果驗證失?。瑧贸绦驅⒇撠熖峁┬铝?。為此,您的應用程序提供了一個URLSession:task:needNewBodyStream:方法。當調用該方法時,您的應用程序應該執(zhí)行任何需要獲取或創(chuàng)建新主體流的操作,然后使用新流調用提供的完成處理程序塊。

注意:因為您的應用必須提供URLSession:task:needNewBodyStream:代理方法(如果它通過流提供主體),此技術與使用系統(tǒng)提供的代理不兼容。

// Listing 1-10  Uploading task from stream example

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];
let textFileURL = URL(fileURLWithPath: "/path/to/file.txt")
if let url = URL(string: "https://www.example.com/") {
    var mutableRequest = MutableURLRequest(url: url)
    mutableRequest.httpMethod = "POST"
    
    let uploadTask = defaultSession.uploadTask(with: mutableRequest, from: textFileURL)
    uploadTask.resume()
}

4. Uploading a File Using a Download Task - 使用下載任務上傳文件

要為下載任務上傳主體內容,您的應用必須提供NSData對象或正文流作為創(chuàng)建下載請求時提供的NSURLRequest對象的一部分。

如果您使用流提供數(shù)據(jù),則應用程序必須提供URLSession:task:needNewBodyStream:代理方法,以在發(fā)生身份驗證失敗時提供新的正文流。 在 Uploading Body Content Using a Stream中進一步描述了此方法。

下載任務的行為就像數(shù)據(jù)任務,除了將數(shù)據(jù)返回給您的應用程序。


Handling Authentication and Custom TLS Chain Validation - 處理身份驗證和自定義TLS鏈驗證

如果遠程服務器返回一個狀態(tài)碼,該狀態(tài)碼指示需要身份驗證,并且該身份驗證需要連接級別質詢(如SSL客戶端證書),則NSURLSession將調用身份驗證質詢代理方法。

注意:Kerberos身份驗證是透明處理的。

當具有基于流的上傳主體的任務的身份驗證失敗時,該任務不一定rewind并安全地重用該流。相反,NSURLSession對象調用委托的URLSession:task:needNewBodyStream:代理方法來獲取為新請求提供主體數(shù)據(jù)的新NSInputStream對象。 (如果任務的上傳主體是從文件或NSData對象提供的,則會話對象不會進行此調用。)

有關為NSURLSession編寫身份驗證代理方法的更多信息,請參閱Authentication Challenges and TLS Chain Validation。


Handling iOS Background Activity - 處理iOS后臺活動

如果您在iOS中使用NSURLSession,則在下載完成時,您的應用程序會自動重新啟動。 您的應用程序的應用程序:application:handleEventsForBackgroundURLSession:completionHandler:應用程序代理方法負責重新創(chuàng)建適當?shù)臅?,存儲完成處理程序,并在會話調用會話代理的URLSessionDidFinishEventsForBackgroundURLSession:方法時調用該處理程序。

Listing 1-11提供了一個在后臺創(chuàng)建和啟動下載任務的例子。 Listing 1-12和Listing 1-13分別顯示了這些會話和應用程序代理方法的示例。

// Listing 1-11  Session background download task for iOS example

SURL *url = [NSURL URLWithString:@"https://www.example.com/"];
 
NSURLSessionDownloadTask *backgroundDownloadTask = [backgroundSession downloadTaskWithURL:url];
[backgroundDownloadTask resume];
if let url = URL(string: "https://www.example.com/") {
    let backgroundDownloadTask = backgroundSession.downloadTask(with: url)
    backgroundDownloadTask.resume()
}
// Listing 1-12  Session delegate methods for iOS background downloads

- (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");
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
    guard let appDelegate = UIApplication.sharedApplication.delegate as? AppDelegate else {
        return
    }
    
    if let completionHandler = appDelegate.backgroundSessionCompletionHandler {
        appDelegate.backgroundSessionCompletionHandler = nil
        completionHandler()
    }
}
// Listing 1-13  App delegate methods for iOS background downloads

@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
class AppDelegate : UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    var backgroundSessionCompletionHandler: CompletionHandler?
    
    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {

后記

本篇主要描述了NSURLSession的使用。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容