Using NSURLSession
NSURLSession API
會話類型
- default session:與其他下載URL的Foundation方法類似,使用基于磁盤的緩存策略,并在用戶的keychain中存儲證書
- Ephemeral session:不存儲任何數據到磁盤中,所有緩存、證書存儲都保存在RAM中并與會話綁定,應用結束會話時,這些都會被釋放
- background session:類似于default session,除了有一個獨立的進程進行來處理所有的數據傳輸。
- 還有個單例 shared session,沒有configuration。
任務類型
- data tasks:使用NSData對象來發送和接收數據。數據任務可以分片返回數據,也可以通過完成處理器一次性返回數據。由于數據任務不存儲數據到文件,所以不支持background session
- download tasks:以文件的形式接收數據,當程序不運行時支持后臺下載
- upload tasks:通常以文件的形式發送數據,支持background session
后臺傳輸
NSURLSession支持在應用掛起時在后臺傳輸數據,后臺傳輸只由使用background session配置的對象創建的會話進行調用,backgroundSessionConfigurationWithIdentifier:
使用后臺會話時,由于其是在一個獨立的進程中傳輸,且重啟應用進程相當損耗資源,只有少量特性可以使用,所以有以下限制:
- 會話必須提供事件分發(event delivery)代理
- 只支持http和https協議
- 總是伴隨重定向
- 只有從文件中upload tasks才可以,從data objects或者stream將會失敗
- 如果當應用在后臺時初始化的后臺傳輸,則配置對象的discretionary屬性為true
在iOS中,當我們的應用不再運行時,如果后臺下載任務完成或者需要證書,則系統會在后臺自動重啟我們的應用,同時調用UIApplicationDelegate對象的application:handlerEventsForBackgroundURLSession:completionHandler:方法。這個調用會提供啟動的應用的session的標識。我們的應用應當存儲completion handler,使用相同的標識來創建后臺配置對象,然后使用配置對象來創建會話。新的會話會與運行的后臺activity關聯。當會話完成后臺下載任務時,會給會話代理發送一個URLSessioinDidFinishEventsForBackgroundURLSession:消息。代理對象然后在main thread調用存儲的completion handler,這樣操作系統才知道再次暫停你的應用是安全的。
如果在程序掛起時有任何任務完成,則會調用URLSession:downloadTask:didFinishDownloadingToURL:方法。
同樣的,如果任務需要證書,則NSURLSession對象會在適當的時候調用URLSession:task:didReceiveChallenge:completionHandler: 和URLSession:didReceiveChallenge:completionHandler:方法。
會話和任務對象實現了NSCopying協議:
- 當應用拷貝一個會話或任務對象時,會獲取相同對象的指針(未創建新對象)
- 當應用拷貝一個配置對象時,會獲取一個可單獨修改的新的對象(創建新對象)
NSURLSession的生命周期
Life Cycle of a URL Session
中文翻譯
創建和配置NSURLSession
配置選項
- 支持對緩存、cookies、證書的私有存儲 ,以及對單例會話特定的protocol
- 關聯到一個特定請求(任務),或者一組請求(會話)的認證
- 可以通過url上傳或者下載文件
- 配置主機的最大連接數
- 當資源無法在一個確定時間內下載時,配置一個超時時間
- 支持安全傳輸協議TLS的版本區間
- 自定義代理字典
- cookie的控制策略
- HTTP的傳輸控制
因為大部分的配置都在一個configuration對象中設置,可以重用一些基本設置;你可以在任何時間安全的修改一個configuration對象。因為當創建一個會話時,configuration對象的傳遞是由深拷貝實現的,所以修改只會影響之后新創建的會話,不會對已存在的會話造成影響。初始化一個會話對象(session object)可以進行如下操作:
- 一個configuration對象用來管理會話或任務的行為
- optional:一個代理對象用來表示接收數據的進度,會話任務或會話其他事件的進度,比如服務器認證,決定一個加載請求是否可轉換為下載請求等等
- 如果沒有指定一個代理,NSURLSession對象將使用系統默認提供的代理。在這種方式中,你可以輕松的使用NSURLSession方法替代已存在的sendAsynchronousRequest:queue:completionHandler:方法
app在后臺傳輸數據,必須自定義代理
Demo——利用配置項創建NSURLSession
- (void)setUpSession
{
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSessionConfiguration *ephemeralConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSessionConfiguration *backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"backgroundConfiguration"];
//針對defaultConfiguration配置cache
NSString *cacheDirectory = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSString *cachePath = [cacheDirectory stringByAppendingString:@"myCache"];
NSURLCache *cacheUrl = [[NSURLCache alloc]initWithMemoryCapacity:16384 diskCapacity:268435456 diskPath:cachePath];
defaultConfiguration.URLCache = cacheUrl;
defaultConfiguration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
NSOperationQueue *operationQueue = [NSOperationQueue mainQueue];
//創建session
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:self delegateQueue:operationQueue];
//...
}
使用系統提供的代理獲取資源
- (void)getDataWithSystemDefaultDelegate
{
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *sessionWithoutADelegate = [NSURLSession sessionWithConfiguration:defaultConfiguration];
NSURL *url = [NSURL URLWithString:@"https://www.example.com/"];
[[sessionWithoutADelegate dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"Got response %@ with error %@.\n", response, error);
NSLog(@"DATA:\n%@\nEND DATA\n", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}] resume];
}
使用自定義的代理獲取資源
需要實現如下2個方法:
URLSession:dataTask:didReceiveData:
URLSession:task:didCompleteWithError:
下載文件
app需要實現以下的代理方法
- URLSession:downloadTask:didFinishDownloadingToURL:提供下載內容臨時存儲的目錄地址。注意:在這個方法返回之前,必須打開文件來進行讀取或者將下載內容移動到一個永久目錄;當方法返回后,臨時文件將會被刪除。
- URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:提供下載的進度信息
- URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:告訴app嘗試恢復之前失敗的下載
- URLSession:task:didCompleteWithError:告訴app下載失敗
- 當下載任務安排在background session中時,當app停止運行時下載繼續;
- 如果是安排在default/ephemeral session中時,當重新開啟app時,下載會重新開始。
- 與服務器傳輸數據期間,如果用戶進行了暫停操作,app可以調用cancelByProducingResumeData:方法取消任務。然后,app可以將已傳輸的數據作為參數傳遞給downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:來創建一個新的下載任務繼續下載。
4)如果傳輸失敗,代理會調用URLSession:task:didCompleteWithError:。如果任務可以再運行,userInfo中包含鍵NSURLSessionDownloadTaskResumeData,可以將數據作為參數傳遞給downloadTaskWithResumeData:或者downloadTaskWithResumeData:completionHandler:來創建一個新的下載任務進行重試
上傳數據內容
app可以對Http post請求提供三種方式的請求體:NSData對象、文件、數據流
NSData上傳
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(NSData *)bodyData;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(NSData *)bodyData
completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
file上傳
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler;
stream上傳
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler;
后臺任務
使用NSURLSession,當下載完成時,app將會自動啟動;代理中2個主要方法:
//負責創建session,存儲completionHandler
- (void)application:(UIApplication *)application
handleEventsForBackgroundURLSession:(NSString *)identifier
completionHandler:(void (^)(void))completionHandler;
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session;