URL Session的使用

URL Session相關類


URL加載

獲取URL的Data數據(存儲在內存)

這裏有兩種基本的方式獲取URL的Data數據。

• 對於簡單的請求,直接使用NSURLSession直接獲取NSURL的數據或者直接使用NSData獲取URL數據。

• 對於複雜的請求,使用NSURLSession和NSURLRequest或者NSMutableURLRequest。

獲取響應數據。

• 提供completion handler block。

• 提供delegate。

下載文件

這裏有兩種基本的方式獲取文件。

• 對於簡單的請求,直接使用NSURLSession直接獲取NSURL的數據或者直接使用NSData獲取URL數據。

• 對於複雜的請求,使用NSURLSession和NSURLRequest或者NSMutableURLRequest。

URL Requests

URL Request用NSURLRequest和NSMutableURLRequest表示。NSURLRequest類封裝了URL和使用的協議以及協議的規定。

響應的元數據

服務器的響應分內容元數據和內容數據。元數據包含MIME類型,內容長度,文本編碼以及URL等信息。NSURLResponse的子類NSHTTPURLResponse提供頭信息和狀態碼。

重定向和請求轉移

例如,在HTTP協議,請求的內容已經轉移到別的地方會告知APP從轉移的地方獲取內容。

驗證和證書

一些服務器會請求用戶提供證書或者用戶名和密碼等信息來獲取內容。證書也可以用來確定是否信任服務器。

注意:存儲到持久化倉庫的證書會保存到鑰匙扣並被其他APP共享。

NSURLCredential類封裝了證書,包含驗證信息(用戶名和密碼)和持久化行爲。NSURLProtectionSpace類代表需要證書的空間,保護空間可以代表URL或者服務器又或者代理。

NSURLCredentialStorage對象管理會話的證書存儲,它提供NSURLCredential和NSURLProtectionSpace的映射。只有在身份驗證質詢成功後,證書纔會存儲。

NSURLAuthenticationChallenge來封裝了需要驗證請求的信息:推薦證書,保護空間,錯誤,失敗響應(用於決定驗證是否需要),驗證嘗試的次數。NSURLAuthenticationChallenge實例指定sender對象來初始化驗證,這個sender必須符合NSURLAuthenticationChallengeSender的協議。

緩存管理

使用NSURLCache來管理URL緩存。它可以設置緩存大小和磁盤位置,包含一組NSCachedURLResponse集合(包含緩存的響應)。NSCachedURLResponse封裝了NSURLResponse和URL內容數據,它提供user info字典來緩存用戶自定義數據。

並不是所有的協議都支持響應緩存。當前只有http和https。

Cookie倉庫

由於http協議是無狀態的,客服端經常使用cookie來持久化一些用戶數據。URL加載系統提供接口創建和管理cookie,發送cookie作爲http請求的一部分和獲取cookie從服務器響應。

NSHTTPCookieStorage管理一組NSHTTPCookie集合。在OS X,Cookie倉庫被所有APP共享。在IOS中,Cookie倉庫只在APP內共享。

協議支持

URL加載系統支持http,https,file,ftp和data協議,你也可以註冊自己的類和應用層網絡協議。


使用NSURLSession

NSURLSession提供一組API實現下載、上傳、驗證、重定向、後臺下載等功能。爲了使用NSURLSession,你的APP創建一系列會話,每個會話可以創建一系列任務,每個任務對應一個網絡請求。你可以使用completion handler block或者delegate獲取響應。

注意:completion handler block是優先於delegate的完成方法,如果設置了block,那麼delegate的完成方法不會執行。

NSURLSession提供狀態和進度屬性,而且分發這些信息到delegate。它支持取消、恢復、暫停任務和恢復那些暫停、取消、失敗的任務。

理解URL Session概念

會話中的任務涉及3個方面:會話的類型(根據NSURLSessionConfiguration的類型),任務的類型,是否APP在前臺。

會話類型

NSURLSession支持3種類型。

• 默認會話。和其他foundation框架的下載方法類似,使用基於磁盤的緩存和鑰匙扣的證書。

• 短暫會話。不會保存任何數據到磁盤;所有緩存、證書等數據都保存在會話的內存中。一旦會話失效,它們就會被清除。

• 後臺會話。和默認會話相似,除了使用新的進程處理數據的傳輸。後臺會話有一些額外的限制。

任務類型

• 數據任務。傳輸和接收數據都是用NSData。數據任務一般用於短的、經常交互的請求。

• 下載任務。獲取數據以文件形式。允許APP不在運行時,進行後臺下載。

• 上傳任務。發送數據以文件形式。允許APP不在運行時,進行後臺上傳。

後臺傳輸注意事項

NSURLSession支持後臺傳輸。你可以使用NSURLSessionConfiguration的後臺會話設置來創建後臺會話。

由於後臺會話是用新的進程來傳輸數據的,而且重新啓動你的APP來處理事件可能會消耗性能。下面是一些限制:

• 會話必須提供一個delegate獲取事件。(對於上傳和下載,delegate的行爲是和進程內傳輸一樣的)

• 只支持http和https。

• 總是服從重定向。

• 上傳任務只支持文件上傳。(在程序退出後,data和stream的上傳會失敗)

• 如果在後臺初始化後臺傳輸。NSURLSessionConfiguration對象的discretionary屬性會設置爲YES。(允許系統優化)

當後臺傳輸完成或者請求證書時,如果你的APP不在運行,IOS會自動在後臺重新啓動APP並調用UIApplicationDelegate的application:handleEventsForBackgroundURLSession:completionHandler:方法。這個方法提供後臺會話的標識以便你重新創建它,新的會話自動關聯後臺任務。你應該保存completionHandler以便在NSURLSession的delegate的URLSessionDidFinishEventsForBackgroundURLSession:方法中調用。completionHandler必須要在主線程調用,它讓系統知道這個後臺任務已經完成和讓APP安全地進入暫停狀態。

注意:你必須明確指定會話的標識,否則會話的行爲可能不確定。

如果後臺任務完成,NSURLSession會調用delegate的URLSession:downloadTask:didFinishDownloadingToURL:方法。

類似地,如果收到證書請求,NSURLSession會調用URLSession:task:didReceiveChallenge:completionHandler:方法或者URLSession:didReceiveChallenge:completionHandler:方法。

當遇到網絡錯誤時,後臺上傳和後臺下載任務會自動重試。

生命週期和delegate交互
NSCoping行爲

NSURLSession和NSURLSessionTask對象是淺複製。

NSURLSessionConfiguration對象是深複製。

Delegate類的接口文件例子
@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
創建和設置會話

NSURLSession的API提供一系列設置選項:

• 支持緩存、Cookie、證書、協議的私有緩存。

• 驗證。會關聯請求(任務)或者一組請求(會話)。

• 通過URL上傳和下載文件,內容數據和元數據分離。

• 設置每個主機最大的連接數量。

• 每個資源的超時時間。

• 最小和最大的TLS版本支持。

• 自定義協議字典。

• 設置Cookie的行爲。

• 設置http管道傳輸行爲。

大多數設置在NSURLSessionConfiguration中,你可以重複使用它。

創建NSURLSession。

• 設置NSURLSessionConfiguration。

• (可選)設置delegate來獲取相關事件。

如果你不提供delegate,系統會默認提供delegate。你可以使用一些便利方法,例如sendAsynchronousRequest:queue:completionHandler:方法。

注意:後臺傳輸必須指定delegate。

在你創建NSURLSession後,你就不能改變會話的configuration和delegate。

// 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];
除了後臺的configuration對象,其它的configuration對象都可以複用。(因爲後臺的configuration對象共用一個標識)

你可以安全地修改configuration對象,因爲configuration對象是深複製的,所以新的對象不會改變原來的那個。

ephemeralConfiguration.allowsCellularAccess = NO;
NSURLSession *ephemeralSessionWiFiOnly = [NSURLSession sessionWithConfiguration:ephemeralConfiguration delegate:delegate delegateQueue:operationQueue];
使用系統提供的delegate獲取資源

最直接使用NSURLSession獲取資源就是使用系統提供的delegate。

• 設置NSURLSession的configuration。

• 提供completion handler處理響應。

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];
使用自定義delegate獲取數據

如果設置自己的delegate,必須至少實現下面兩個方法:

• URLSession:dataTask:didReceiveData:方法。

• URLSession:task:didCompleteWithError:方法。

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

下載文件和獲取數據類似。

• URLSession:downloadTask:didFinishDownloadingToURL: 提供下載文件的臨時存儲URL。

注意:在這個方法返回前,必須讀取或者移動臨時存儲文件到保存地方。當該方法返回時,臨時存儲文件會被刪除。

• URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: 提供下載進度信息。

• URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:告訴你恢復之前失敗的上傳任務被成功恢復了。

• URLSession:task:didCompleteWithError:告訴你下載失敗。

如果你使用後臺會話執行下載任務,即使APP不再運行下載也會繼續。如果你使用的是默認或者短暫會話,重新啓動APP時必須重新下載。

你可以在下載時使用下載任務的cancelByproducingResumeData:方法來暫停任務,之後,你可以使用這個方法返回的Resume Data和downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:方法創建新的下載任務來繼續下載。

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];
- (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]) {
        // ...
    }
}
上傳請求體內容

上傳請求體內容分3種:NSData對象,文件,stream流。

• 如果你已經有上傳內容的NSData對象而且不需要進一步處理,你應該使用NSData對象。

• 如果上傳內容是已經在磁盤存在的文件,或者你需要後臺傳輸,又或者你比較容易地進行寫文件操作,你應該使用文件形式。

• 如果上傳內容從網絡上接收,你應該使用stream流形式。

如果你使用stream流形式,你必須實現URLSession:task:needNewBodyStream:代理方法。

上傳數據使用NSData對象

上傳NSData數據,你需要調用updateTaskWithRequest:fromData:或者updateTaskWithRequest:fromData:completionHandler:方法來創建上傳任務。

會話對象會計算Content-Length頭根據數據內容。

你的APP需要提供其他的請求頭,例如Content-Type。

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];
上傳數據使用文件形式

上傳文件數據,你需要調用uploadTaskWithRequest:fromFile:或者updateTaskWithRequest:fromFile:completionHandler:方法來創建上傳任務。

會話對象會計算Content-Length頭根據數據內容。

你的APP需要提供其他的請求頭,例如Content-Type。

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];
上傳數據使用stream流形式

上傳stream流數據,你需要調用uploadTaskWithStreamedRequest:方法來創建上傳任務。

你的APP需要提供其他的請求頭,例如Content-Type,Content-Length。

因爲會話的流不能夠恢復,你必須實現URLSession:task:needNewBodyStream:代理方法來提供一個新的流。

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];
使用下載任務來上傳文件

你必須使用NSData對象或者Stream流形式創建下載任務。


處理驗證和自定義TLS鏈驗證

當收到服務器驗證時,下面代理方法會被調用:

• 會話級別的Challenge-NSURLAuthenticationMethodNTLM,NSURLAuthenticationMethodNegotiate,NSURLAuthenticationMethodClientCertificate後者NSURLAuthenticationMethodServerTrust。 NSURLSession對象會調用URLSession:didReceiveChallenge:completionHandler:代理方法。如果你的APP沒有提供NSURLSession的delegate對象,NSURLSession會調用URLSession:task:didReceiveChallenge:completionHandler:方法。

• 非會話級別的Challenge,NSURLSession會調用任務的URLSession:task:didReceiveChallenge:completionHandler:方法。會話的URLSession:didReceiveChallenge:completionHandler:代理方法不會由於非會話級別的Challenge而被調用,你必須實現任務級別的代理方法。


處理IOS後臺活動

如果你使用NSURLSession,當下載任務完成後,你的APP會自動重新啓動。APP的application:handleEventsForBackgroundURLSession:completionHandler:代理方法會被調用。你應該獲取適當的Session和存儲completionHandler,以便在NSURLSession的URLSessionDidFinishEventsForBackgroundURLSession:代理方法中調用。

NSURL *url = [NSURL URLWithString:@"https://www.example.com/"];
 
NSURLSessionDownloadTask *backgroundDownloadTask = [backgroundSession downloadTaskWithURL:url];
[backgroundDownloadTask resume];
- (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");
}
@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

編碼和解碼URL數據

爲了實現percent-encode URL字符串,調用stringByAddingPercentEncodingWithAllowedCharacters:方法和提供適當的字符串集。

• User:URLUserAllowedCharacterSet

• Password:URLPasswordAllowedCharacterSet

• Host:URLHostAllowedCharacterSet

• Path:URLPathAllowedCharacterSet

• Fragment:URLFragmentAllowedCharacterSet 

• Query:URLQueryAllowedCharacterSet

注意:不要用stringByAddingPercentEncodingWithAllowedCharacters:方法來編碼整個URL字符串,因爲URL字符串每一部分都有不同的規則。

NSString *originalString = @"color-#708090";
NSCharacterSet *allowedCharacters = [NSCharacterSet URLFragmentAllowedCharacterSet];
NSString *percentEncodedString = [originalString stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters];
NSLog(@"%@", percentEncodedString"); // prints "color-%23708090"

如果你想要編碼URL字符串組件,你需要使用NSURLComponents來劃分組件。

NSURL *URL = [NSURL URLWithString:@"https://example.com/#color-%23708090"];
NSURLComponents *components = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:NO];
NSString *fragment = components.fragment;
NSLog(@"%@", fragment); // prints "color-#708090"


處理重定向和其它請求變化

重定向是服務器響應請求指示客戶端應該使用新的URL獲取內容。你需要實現URLSession:task:willPerformHTTPR額direction:newRequest:completionHandler:代理方法來處理該事件。

在這個代理方法你可以:

• 允許重定向。直接返回提供的請求。

• 創建新的請求返回。

• 拒絕重定向。返回nil。

而且你也可以取消重定向和連接,直接在代理方法中取消任務(調用cancel方法)。

- (void)URLSession:(NSURLSession *)session
        task:(NSURLSessionTask *)task
        willPerformHTTPRedirection:(NSHTTPURLResponse *)redirectResponse
        newRequest:(NSURLRequest *)request
        completionHandler:(void (^)(NSURLRequest *))completionHandler
{
    NSURLRequest *newRequest = request;
    if (redirectResponse) {
        newRequest = nil;
    }
 
    completionHandler(newRequest);
}

如果沒有實現這個代理方法,所有的重定向默認允許。


驗證Challenge和TLS鏈

決定怎樣響應驗證Challenge

這裏有3種方式響應:

• 提供驗證證書。

• 嘗試不用證書驗證。

• 取消驗證。

NSURLAuthenticationChallenge實例包含一些信息:導致驗證Challenge觸發的原因,嘗試的次數,之前嘗試的證書。NSURLProtectionSpace請求證書和Challenge發送者。

如果驗證Challenge之前驗證失敗,(例如,用戶修改了密碼)你可以從Challenge的proposedCredential屬性獲取之前嘗試的證書。在這個代理方法,你可以使用這些證書來獲取歷史記錄。

調用Challenge對象的previousFailureCount屬性獲取之前總共失敗的次數。你可以利用這個屬性來確定之前嘗試的證書是否失敗過後者確定在嘗試多少次。

響應驗證Challenge

下面是3種方法響應URLSession:didReceiveChallenge:completionHandler或者URLSession:task:didReceiveChallenge:completionHandler:代理方法。

提供證書

爲了提供驗證,你應該創建一個NSURLCredential對像。首先你先要確定驗證方法是什麼?你可以通過college的protectionSpace的authenticationMethod屬性獲取驗證方法。

• HTTP基本驗證(NSURLAuthenticationMethodHTTPBasic)請求用戶名和密碼。提示用戶輸入必要信息並調用credentialWithUser:password:persistence:方法創建NSURLCredential。

• HTTP摘要驗證(NSURLAuthenticationMethodHTTPDigest),像基本驗證需要用戶名和密碼。(摘要自動生成)使用credentialWithUser:password:persistence:方法創建。

• 客戶端證書驗證(NSURLAuthenticationMethodClientCertificate)請求系統標識和服務器需要驗證的證書。使用credentialWithIdentity:certificates:persistence:方法創建。

• 服務器信任驗證(NSURLAuthenticationMethodServerTrust)請求Challenge的protectionSpace的serverTrust。使用credentialForTrust:方法創建。

在你創建完NSURLCredential對象,傳遞NSURLCredential對象到completion handler block。

不使用證書

如果代理對象不提供證書給驗證Challenge,這會嘗試不使用證書完成驗證。傳遞下面的值給completion handler block:

• NSURLSessionAuthChallengePerformDefaultHandling 系統默認的處理驗證請求的方式(如果不實現代理方法使用該方式)。

• NSURLSessionAuthChallengeRejectProtectionSpace 拒絕challenge。根據服務器允許的驗證方式,URL加載系統會調用該代理方法多次。

取消連接

你可以通過傳遞NSURLSessionAuthChallengeCancelAuthenticationChallenge給completion handler block來取消連接。

驗證例子

通過提供用戶名和密碼來創建NSURLCredential來響應請求。如果失敗,取消驗證challenge並通知用戶。

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler
{
    if ([challenge previousFailureCount] == 0) {
        NSURLCredential *newCredential = [NSURLCredential credentialWithUser:[self preferencesName]
                                                                    password:[self preferencesPassword]
                                                                 persistence:NSURLCredentialPersistenceNone];
        completionHandler(NSURLSessionAuthChallengeUseCredential, newCredential);
    } else {
        // Inform the user that the user name and password are incorrect
        completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
    }
}

如果這個驗證challenge沒有被會話和任務代理處理而且沒有可用的證書或者驗證失敗,continueWithoutCredentialForAuthenticationChallenge:方法會被底層實現並調用。

執行自定義的TLS鏈驗證

在NSURL類族AIP中,TLS鏈驗證需要你的APP實現驗證代理方法,並不是提供證書驗證服務器(或者你的APP),你的APP需要在TLS握手中驗證服務器證書,之後告訴URL加載系統是否接收或者拒絕證書。

如果你需要使用非標準的方式來執行鏈驗證(例如,接收指定的自簽名證書來測試),你的APP必須實現 URLSession:didReceiveChallenge:completionHandler:或者 URLSession:task:didReceiveChallenge:completionHandler:代理方法,如果你同時實現,會話級別的方法有責任處理驗證。

在代理方法中,你應該檢查challenge的保護空間的驗證方法是否是NSURLAuthenticationMethodServerTrust,如果是,從保護空間獲取serverTrust信息。


理解緩存獲取

URL加載系統提供複雜的磁盤和內存的響應緩存。緩存允許應用減少網絡連接的依賴和提高性能。

對請求使用緩存

NSURLRequest對象可以指定本地緩存的怎樣使用,你可以設置緩存政策(NSURLRequestCachePolicy):NSURLRequestUseProtocolCachePolicy,NSURLRequestReloadIgnoringCacheData,NSURLRequestReturnCacheDataElseLoad,NSURLRequestReturnCacheDataDontLoad。

NSURLRequest對象的默認緩存政策是NSURLRequestUseProtocolCachePolicy。這個政策是協議指定的而且是效果最佳的。

如果設置緩存政策爲NSURLRequestReloadIgnoringCacheData,URL加載系統會直接從指定的URL加載數據,完全避免使用緩存。

如果設置緩存政策爲NSURLRequestReturnCacheDataElseLoad,URL加載系統會先尋找緩存不管緩存的生存期或者過期日期,如果沒有命中緩存則加載數據。

如果這隻緩存政策爲NSURLRequestReturnCacheDataDontLoad,URL加載系統只會從緩存獲取響應,如果沒有命中緩存則直接返回nil。這個政策類似於離線模式,不會連接網絡獲取數據。

注意:目前只有HTTP和HTTPS協議提供響應緩存,其它協議不提供響應緩存,直接加載URL指定數據。

HTTP協議的緩存使用語義

最複雜的緩存使用情形是HTTP協議使用NSURLRequestUseProtocolCachePolicy緩存政策。

如果這個請求的NSCachedURLResponse不存在,URL加載系統從指定的URL加載數據。

如果這個請求的NSCachedURLResponse存在,URL加載系統需要檢查這個響應緩存的內容是否被指定爲重新驗證。

如果這個內容必須重新驗證,URL加載系統會向指定的URL數據源發送HEAD請求來確定內容是否改變。如果沒有改變,URL加載系統會返回這個響應緩存。如果已經改變,URL加載系統會加載數據。

如果響應緩存的內容沒有指定爲重新驗證,URL加載系統會檢查緩存響應的生存期或者過期時間。如果響應緩存時最近的,URL加載系統會返回這個響應緩存。如果響應緩存是久的,URL加載系統會向指定的URL數據源發送HEAD請求來確定內容是否改變,如果是,URL加載系統會加載數據,否則,會返回響應緩存。

詳細請查看http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13

程序控制緩存

通常,請求的數據會根據緩存政策來進行緩存。

如果你的APP需要精確的程序控制緩存(如果這個協議支持緩存),你可以實現代理方法來決定每一個請求的指定響應是否被緩存。

對於NSURLSession的數據和上傳任務,實現URLSession:dataTask:willCacheResponse:completionHandler: 方法。這個代理方法只會在數據和上傳任務調用。下載任務的緩存政策需要專門提供。

對於NSURLSession,你的代理方法需要調用completion handler block來告訴會話緩存什麼。代理通常會提供下面一些值:

• 提供允許緩存的響應對象。

• 新建一個響應緩存對象來緩存修改的響應 - 例如,響應的存儲政策爲內存緩存而不是磁盤緩存。

• 提供nil來阻止緩存。

你的代理方法可以向響應緩存對象的userInfo字典插入對象,會造成這些對象會和響應緩存對象一起被緩存。

注意:你的代理方法必須調用completion handler block,否則會造成內存泄漏。

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse * __nullable cachedResponse))completionHandler {
    NSCachedURLResponse *newCachedResponse = proposedResponse;
    NSDictionary *newUserInfo;
    newUserInfo = [NSDictionary dictionaryWithObject:[NSDate date]
                                              forKey:@"Cached Date"];
    if ([proposedResponse.response.URL.scheme isEqualToString:@"https"]) {
#if ALLOW_IN_MEMORY_CACHING
        newCachedResponse = [[NSCachedURLResponse alloc]
                             initWithResponse:proposedResponse.response
                             data:proposedResponse.data
                             userInfo:newUserInfo
                             storagePolicy:NSURLCacheStorageAllowedInMemoryOnly];
#else // !ALLOW_IN_MEMORY_CACHING
        newCachedResponse = nil;
#endif // ALLOW_IN_MEMORY_CACHING
    } else {
        newCachedResponse = [[NSCachedURLResponse alloc]
                             initWithResponse:[proposedResponse response]
                             data:[proposedResponse data]
                             userInfo:newUserInfo
                             storagePolicy:[proposedResponse storagePolicy]];
    }
 
    completionHandler(newCachedResponse);
}

Cookies和自定義協議

如果你的APP需要程序上管理cookies,例如添加和刪除cookies或者決定哪個cookies應該被獲取。

如果你的APP需要一個基本的URL協議,但是NSURL不支持。你可以註冊你自己的自定義協議類來提供這個需求。

Cookie存儲

由於HTTP協議是無狀態的,客戶端經常使用cookie來提供持久化存儲URL請求數據。URL加載系統提供接口來創建和管理cookies,發送cookie作爲HTTP請求的一部分,解析響應獲取cookie。

NSHTTPCookie封裝cookie,它提供大多數通用cookie屬性的訪問方法。這個類提供HTTP的cookie頭和NSHTTPCookie實例互相轉換的方法。URL加載系統自動發送對應NSURLRequest的存儲cookie,除非指定不發生cookie。同樣的,cookie會根據訪問政策返回NSURLResponse。

NSHTTPCookieStorage提供接口去管理一組NSHTTPCookie,這些NSHTTPCookie對象可以被所有APP訪問。

注意:IOS不支持Cookie在APP間共享。

NSHTTPCookieStorage允許APP指定cookie訪問訪問政策。cookie訪問政策控制包含可訪問、不可訪問、只能被相同域(主文檔URL)訪問。

注意:在APP中改變cookie訪問政策也會作用於其他APP。

當另外的APP改變cookie存儲或者cookie訪問政策,NSHTTPCookieStorage會發生NSHTTPCookieManagerCookiesChangedNotification和NSHTTPCookieStorageAcceptPolicyChangedNotification通知。

協議支持

URL加載系統允許客戶端APP去擴展傳輸數據協議。URL加載系統本地支持http、https、file、ftp、data協議。

你可以提供繼承NSURLProtocol來實現自定義協議並使用NSURLProtocol的registerClass:方法來註冊新的類。當NSURLSession對象初始化NSURLRequest連接,URL加載系統會按註冊的反順序詢問註冊的類。這個類在canInitWithRequest:返回YES,它將會用來處理請求。

如果你的自定義協議需要爲請求和響應添加額外的屬性,你可以創建NSURLRequest,NSMutableURLRequest,NSURLResponse種類來添加額外屬性的訪問方法。NSURLProtocol提供方法來使用這些方法設置和獲取額外的屬性值。

在連接開始和完成時,URL加載系統負責創建和釋放NSURLProtocol實例。你的APP不要直接創建NSURLProtocol實例。

當NSURLProtocol子類被URL加載系統初始化,它提供一個符合NSURLProtocolClient協議的客戶端。NSURLProtocol子類會從NSURLProtocolClient協議客戶端發送消息去通知URL加載系統,這些包括創建響應,接收數據,重定向新URL,請求驗證,加載完成。如果自定義協議支持驗證,它必須實現NSURLAuthenticationChallengeSender協議。


URL會話的聲明週期

你可以使用NSURLSession的API的兩個方式:系統提供delegate或者你自己提供delegate。通常,如果你要完成下面的任務,你必須使用自己的delegate:

• 當你的APP不在運行時,使用後臺會話來下載或者上傳內容。

• 執行自定義驗證。

• 執行自定義的SSL證書驗證。

• 確定傳輸是否應該下載到磁盤或者根據MIME類型來展示。

• 上傳數據來自數據流。

• 程序上限制緩存。

• 程序上限制HTTP重定向。

如果你的APP不需要做任何這樣的事情,你的APP可以使用系統提供的delegate。

系統提供delegate的URL會話聲明週期

如果你使用NSURLSession而且不提供delegate對象,系統提供的delegate對象會處理大部分細節。下面是你必須提供的基本的方法調用順序和提供完成handler。

1. 創建一個會話設置(session configuration)。對於後臺會話,這個會話設置必須設置一個唯一標識。保存這個標識,在應用崩潰、終止、暫停使用它。

2.創建一個會話,指定會話設置對象或者nil。

3.使用會話創建任務對象,這個對象代表資源請求。

每一個任務開始狀態爲暫停。在你調用任務的resume方法後,它開始下載指定的資源。

NSURLSessionTask任務對象的子類-NSURLSessionDataTask,NSURLSessionUploadTask,NSURLSessionDownloadTask。

雖然你的APP可能(應該)在會話中添加超過一個任務,簡單起見,接下來的步驟以一個單獨的任務來說明聲明週期。

注意:如果你使用NSURLSession並且不提供delegate對象,你必須使用completionHandler來獲取數據。

4.對於下載任務,在傳輸過程中,如果用戶告訴你暫停任務,調用cancelByProducingResumeData:方法來取消任務。之後,把恢復數據(Resume Data)傳遞給downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:方法來創建一個新的下載任務來繼續下載。

5.當任務完成後,NSURLSession對象會調用任務的completionHandler。

注意:NSURLSession不會提供error參數來報告服務器錯誤。你的APP只會接收到客戶端這邊的錯誤信息,例如,不能解析主機名或者不能連接主機。

服務器那邊的錯誤會通過NSHTTPURLResponse的HTTP狀態碼來表示。

6.當你的APP不再需要會話,通過invalidateAndCancel(取消當前任務)或者finishTasksAndInvalidate(允許當前任務在失效會話前完成)方法來失效會話。

自定義delegate的URL會話生命週期

你可能經常提供delegate對象來使用NSURLSession。但是,如果你使用NSURLSession進行後臺下載和上傳,或者如果你需要處理驗證或者使用非默認的緩存行爲,你必須提供一個會話代理對象,一個或者多個任務代理對象,或者符合這些代理協議的代理對象。

• 當你使用下載任務,NSURLSession對象提供delegate來提供下載完成的數據的本地URL。

這些代理對象必須實現NSURLSessionDownloadDelegate協議。

• 代理能夠處理一些驗證評估。

• 代理提供上傳流數據的body流。

• 代理能夠決定是否跟隨HTTP重定向。

• NSURLSession對象提供delegate來提供傳輸狀態。數據任務(Data Task)的代理接收初始化回調,這個回調能轉換請求爲下載任務和接收一系列的下載數據進程調用。

• NSURLSession告訴代理下載完成。

如果你提供自定義代理對象給URL會話(請求後臺任務),完成的URL會話生命週期會更復雜。下面是一系列方法調用,這些方法你必須實現。

1.創建會話設置(session configuration)。對於後臺會話,這個會話設置必須設置一個唯一標識。保存這個標識,在應用崩潰、終止、暫停使用它。

2.創建會話(session),指定設置對象和代理對象。

3.使用會話創建任務對象,這個對象代表資源請求。

每一個任務開始狀態爲暫停。在你調用任務的resume方法後,它開始下載指定的資源。

NSURLSessionTask任務對象的子類-NSURLSessionDataTask,NSURLSessionUploadTask,NSURLSessionDownloadTask。

雖然你的APP可能(應該)在會話中添加超過一個任務,簡單起見,接下來的步驟以一個單獨的任務來說明聲明週期。

注意:如果你使用NSURLSession並且不提供delegate對象,你必須使用completionHandler來獲取數據。

4.如果遠程服務器返回狀態碼來指示驗證請求而且如果驗證請求是連接階段的諮詢(challenge)(例如SSL客戶端證書),NSURLSession調用驗證諮詢(authentication challenge)代理方法。

• 對於會話級別的諮詢-NSURLAuthenticationMethodNTLM,NSURLAuthenticationMethodNegotiate,NSURLAuthenticationMethodClientCertificate,NSURLAuthenticationMethodServerTrust-NSURLSession對象會調用會話代理的URLSession:didReceiveChallenge:completionHandler:方法。如果你的APP沒有實現這個方法,NSURLSession對象會調用任務代理的URLSession:task:didReceiveChallenge:completionHandler:方法來處理諮詢。

• 對於不是會話級別的諮詢,NSURLSession對象會調用任務代理的URLSession:task:didReceiveChallenge:completionHandler:方法來處理諮詢。你必須提供任務代理的驗證方法或者提供在任務級別handler調用會話級別的handler。會話代理的URLSession:didReceiveChallenge:completionHandler:方法不會被不是會話級別的諮詢觸發。

如果上傳任務驗證失敗,如果任務的數據是流數據,NSURLSession對象會調用URLSession:task:needNewBodyStream:代理方法。這個代理方法必須提供一個新的NSInputStream對象來獲取body數據。

5.當收到HTTP重定向響應,NSURLSession對象會調用代理的URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:方法。這個代理方法提供completion handler;你需要提供NSURLRequest對象(使用提供的newRequest)給completion handler,或者是創建一個新的NSURLRequest對象,又或者是nil(不進行重定向)。

• 如果跟隨重定向,回到第4步(驗證諮詢處理)。

• 如果代理不實現這個方法,默認跟隨重定向。

6.對於恢復下載任務((re-)download task),使用downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:方法來重新創建恢復下載任務。NSURLSession對象會調用代理的URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:方法來表示下載任務恢復成功。

7.對於數據任務(Data task),NSURLSession對象調用代理的URLSession:dataTask:didReceiveResponse:completionHandler:方法。決定是否轉換數據任務爲下載任務,之後調用completion callback來繼續獲取數據或者下載數據。

如果你的APP選擇轉換數據任務爲下載任務,NSURLSession會調用代理的URLSession:dataTask:didBecomeDownloadTask:方法來獲取新的下載任務對象。在這個方法調用成功後,代理不會收到任何數據任務的回調並開始接收下載任務的回調。

8.如果使用uploadTaskWithStreamedRequest:方法創建任務,NSURLSession對象會調用URLSession:task:needNewBodyStream:方法來提供body數據。

9.在進行上傳任務時,代理會週期性調用URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:回調來報告上傳進程。

10.在進行下載任務時,代理會週期性調用URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:方法來報告下載進程。對於數據任務,會話會調用代理的URLSession:dataTask:didReceiveData:方法來獲取每次實際接收的數據。

對於下載任務,如果用戶暫停下載,調用cancelByProducingResumeData:方法來取消任務。

之後,如果用戶恢復下載,提供恢復數據並調用downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:方法來創建新的下載任務來繼續下載,之後回到步驟3(創建和恢復任務對象)。

11.對於數據任務,NSURLSession對象會調用代理的URLSession:dataTask:willCacheResponse:completionHandler:方法。你的APP需要決定是否允許緩存。如果你沒有實現這個方法,默認行爲會根據會話設置對象的緩存政策。

12.如果下載任務成功完成,NSURLSession對象會調用URLSession:downloadTask:didFinishDownloadingToURL:方法,這個方法會提供下載文件的臨時存儲URL。你的APP可以讀取這個文件的內容或者在這個方法返回前,移動這個文件到沙盒的某個地方。

13.如果任務完成,NSURLSession對象會調用代理的URLSession:task:didCompleteWithError:方法。

如果任務失敗,大多數APP應該重新嘗試請求直到用戶取消下載或者服務器返回錯誤指示請求不可能成功。你的APP不應該馬上重新請求,而是使用reachability API來決定是否服務器是可訪問的和在收到服務器可訪問通知時才創建新的請求。

如果下載任務能夠恢復,NSError對象的userInfo字典會包含NSURLSessionDownloadTaskResumeData鍵值對。你的APP應該是這個鍵的值並調用downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:來創建新的下載任務來繼續下載。

如果任務不能夠恢復,你的APP應該創建新的下載任務並重新啓動新的事務。

如果傳輸失敗由於各種原因除了服務器錯誤,回到步驟3(創建並恢復任務對象)。

14.如果響應是multipart編碼,會話可能會再次調用代理的didReceiveResponse方法,之後是0次或者更多次地調用didReceiveData方法。如果發生,回到步驟7(處理didReceiveResponse方法)。

15.當你的APP不再需要會話,通過invalidateAndCancel(取消當前任務)或者finishTasksAndInvalidate(允許當前任務在失效會話前完成)方法來失效會話。

在失效會話後,當所有當前任務被取消或者完成,會話會調用代理的URLSession:didBecomeInvalidWithError:方法。當代理方法返回,會話會處理代理的強引用。

注意:會話會保持代理的強引用直到你明確失效會話。如果你沒有失效會話,你的APP會內存泄露。

如果你的APP取消正在下載任務,NSURLSession對象會調用代理的URLSession:task:didCompleteWithError: 方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章