標籤:
簡單的代理類的介面
下面的程式碼片段基於清單1-1所示介面
清單1-1
#import <Foundation/Foundation.h>typedef void (^CompletionHandlerType)();@interface MySessionDelegate : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>@property NSURLSession *backgroundSession;@property NSURLSession *defaultSession;@property NSURLSession *ephemeralSession;#if TARGET_OS_IPHONE@property NSMutableDictionary *completionHandlerDictionary;#endif- (void) addCompletionHandler: (CompletionHandlerType) handler forSession: (NSString *)identifier;- (void) callCompletionHandlerForSession: (NSString *)identifier;@end
建立並配置一個會話
NSURLSession提供了大量的配置選項:
- 支援對緩衝,cookies,認證的私人儲存,以及對單例會話的特定協議
- 關聯到一個特定請求(任務),或者一組請求(會話)的認證
- 通過URL上傳或下載檔案,支援將中繼資料分割成基於檔案內容的短資料
- 配置每個主機的最大串連數
- 當資源無法在一個確定時間內下載時,配置一個逾時時間
- 支援安全傳輸層協議(TLS)的版本區間
- 自訂代理
- cookie的管理原則
- HTTP傳輸管理
大部分的配置都在一個configuration對象中設定,可以通用一些基本設定.初始化一個會話對象(session object)需要指定如下資訊:
- 一個configuration對象用來管理會話或任務的行為
- 可選的,一個代理對象用來表示接收資料的進度,會話任務或會話其他事件的進度,比如伺服器認證,決定一個載入請求是否可轉換為下載請求,等等
如果沒有指定一個代理,NSURLSession對象將使用系統提供得代理.在這種方式中,你可以輕鬆的使用NSURLSession替代已存在的sendAsynchronousRequest:queue:completionHandler:方法.
注意:如果app需要在後台進行資料轉送,必須使用自訂代理.
建立一個會話對象之後,不能再去修改它的configuration對象和代理,除了重新建立一個會話.
清單1-2展示了建立預設會話,臨時會話和後台會話的範例程式碼
#if TARGET_OS_IPHONEself.completionHandlerDictionary = [NSMutableDictionary dictionaryWithCapacity:0];#endif/* Create some configuration objects. */NSURLSessionConfiguration *backgroundConfigObject = [NSURLSessionConfiguration backgroundSessionConfiguration: @"myBackgroundSessionIdentifier"];NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];NSURLSessionConfiguration *ephemeralConfigObject = [NSURLSessionConfiguration ephemeralSessionConfiguration];/* Configure caching behavior for the default session. Note that 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_IPHONENSString *cachePath = @"/MyCacheDirectory";NSArray *myPathList = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);NSString *myPath = [myPathList objectAtIndex:0];NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];NSString *fullCachePath = [[myPath stringByAppendingPathComponent:bundleIdentifier] stringByAppendingPathComponent:cachePath];NSLog(@"Cache path: %@\n", fullCachePath);#elseNSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"/nsurlsessiondemo.cache"];NSLog(@"Cache path: %@\n", cachePath);#endifNSURLCache *myCache = [[NSURLCache alloc] initWithMemoryCapacity: 16384 diskCapacity: 268435456 diskPath: cachePath];defaultConfigObject.URLCache = myCache;defaultConfigObject.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;/* Create a session for each configurations. */self.defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];self.backgroundSession = [NSURLSession sessionWithConfiguration: backgroundConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];self.ephemeralSession = [NSURLSession sessionWithConfiguration: ephemeralConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
除了後台設定物件(background configurations),可以重用設定物件來建立其他的會話.(不能重用後台設定物件是因為兩個後台會話不能使用相同的標識符identifier)
你可以在任何時間安全的修改一個configuration對象.因為當建立一個會話時,configuration對象的傳遞是由深拷貝實現的,所以修改只會影響之後建立的會話,不會對已存在的會話造成影響.例如,你可能想建立另一個只有在WiFi環境下才能重連資料的會話,如1-3中所示:
清單1-3重用configuration對象
ephemeralConfigObject.allowsCellularAccess = YES;// ...NSURLSession *ephemeralSessionWiFiOnly = [NSURLSession sessionWithConfiguration: ephemeralConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
使用系統提供的代理抓取資源
最簡單直接的使用NSURLSession的方法是用來替換掉之前的sendAsynchronousRequest:queue:completionHandler:方法.要這麼做,你需要在app中實現兩處代碼:
- 建立configuration對象,以及一個基於該configuration對象的會話對象
- 一個完成處理常式來處理資料接收完成後要做的事情
使用系統提供的代理,你可以每個請求只用一行代碼來抓取特定URL.清單1-4樣本了最簡單的實現.
注意:系統提供的代理僅僅實現了有限網路功能.如果app的需求超出了基本的URL載入,比如自訂認證或者資料後台下載,那麼需要實現一個完整的代理,參見URL Session的生命週期.
清單1-4使用系統提供的代理請求資源:
NSURLSession *delegateFreeSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];[[delegateFreeSession dataTaskWithURL: [NSURL URLWithString: @"http://www.example.com/"] 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:提供了工作要求返回的資料,周期性的返回資料區塊
- URLSession:task:didCompleteWithError:表明資料是否全部完成接收
如果app需要在URLSession:dataTask:didReceiveData:方法返回之後使用資料,必須用代碼實現資料存放區.
例如,一個web瀏覽器可能需要根據之前接收的資料來渲染當前接收的資料.要實現這個功能可以使用一個NSMutableData對象來儲存結果資料,然後使用 appendData: 來將當前接收的資料拼接到之前接收到的資料中.
清單1-5樣本了如何建立開始一個資料任務:
NSURL *url = [NSURL URLWithString: @"http://www.example.com/"];NSURLSessionDataTask *dataTask = [self.defaultSession dataTaskWithURL: url];[dataTask resume];
下載檔案
某種程式上,下載檔案和接收資料類似.app應當實現以下的代理方法:
- URLSession:downloadTask:didFinishDownloadingToURL:提供app下載內容的臨時儲存目錄. 注意:在這個方法返回之前,必須開啟檔案來進行讀取或者將下載內容移動到一個永久目錄.當方法返回後,臨時檔案將會被刪除.
- URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite: 提供了下載進度的狀態資訊.
- URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes: 告訴app嘗試恢複之前失敗的下載.
- URLSession:task:didCompleteWithError:告訴app下載失敗
如果將下載任務安排在後台會話中,在app非運行期間下載行為仍將繼續.如果將下載任務安排在系統預設會話或者臨時會話中,當app重新啟動時,下載也將重新開始.
在跟伺服器傳輸資料期間,如果使用者進行了暫停操作,app可以調用cancelByProducingResumeData: 方法取消任務.然後,app可以將已傳輸的資料作為參數傳遞給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 = [self.backgroundSession downloadTaskWithURL: url];[downloadTask resume];
清單1-7 下載任務的代理方法:
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{ NSLog(@"Session %@ download task %@ finished downloading to URL %@\n",session, downloadTask, location);#if 0/* Workaround */[self callCompletionHandlerForSession:session.configuration.identifier];#endif#define READ_THE_FILE 0#if READ_THE_FILE/* Open the newly downloaded file for reading. */NSError *err = nil;NSFileHandle *fh = [NSFileHandle fileHandleForReadingFromURL:location error: &err];/* Store this file handle somewhere, and read data from it. */// ...#elseNSError *err = nil;NSFileManager *fileManager = [NSFileManager defaultManager];NSString *cacheDir = [[NSHomeDirectory() stringByAppendingPathComponent:@"Library"] stringByAppendingPathComponent:@"Caches"];NSURL *cacheDirURL = [NSURL fileURLWithPath:cacheDir];if ([fileManager moveItemAtURL:location toURL:cacheDirURL error: &err]) { /* Store some reference to the new URL */} else { /* Handle the error. */}#endif}-(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);}
上傳資料內容
app能通過三種方式通過提供HTTP POST請求體:NSData對象,檔案和流.通常情況下,app應當:
- 使用一個NSData對象,如果記憶體中已經存在相應資料,並且資料不會被無理由的銷毀.
- 使用檔案形式,如果要上傳的內容是通過檔案形式儲存在硬碟中的,或者將要上傳的內容寫入檔案,要是這樣可以解決記憶體的話.
- 使用流,如果是從網路接收資料,或者轉化已存在的提供了流的NSURLConnection代碼.
無論你選擇了哪種方法,如果app提供了自訂代理,都應該實現URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend: 方法來擷取上傳進度.
此外,如果app使用流作為請求體,還必須提供一個自訂會話代理實現URLSession:task:needNewBodyStream:方法,詳細描述在通過流上傳資料
使用NSData對象上傳
使用NSData對象上傳資料,app需要調用uploadTaskWithRequest:fromData:或者uploadTaskWithRequest:fromData:completionHandler:來建立一個上傳任務,將要上傳的NSData對象傳遞給fromData參數.
會話對象根據NSData對象計算內容長度,賦值給要求標頭的Content-Length.app還要在URL request對象中提供伺服器可能需要的要求標頭資訊-例如:content type.
使用檔案形式上傳
使用檔案形式上傳,app需要調用 uploadTaskWithRequest:fromFile:或者uploadTaskWithRequest:fromFile:completionHandler: 方法來建立一個上傳任務,以及一個檔案路徑來讀取內容.
會話對象自動計算Content-Length,如果app沒有提供 Content-Type,會話對象將自動產生一個.app還要在URL request對象中提供伺服器可能需要的要求標頭資訊
使用流形式上傳
使用流來上傳資訊,app需要調用 uploadTaskWithStreamedRequest: 方法來建立一個上傳任務.app提供一個綁定了流的request對象.app還要在URL request對象中提供伺服器可能需要的要求標頭資訊,比如content-type和content-length.
此外,因為會話對象不能保證必定能從提供的流中讀取資料,所以app需要提供一個新的流以便會話重新進行請求(比如,認證失敗).app需要實現 URLSession:task:needNewBodyStream:方法.當這個方法被調用時,app需要取得或者建立一個新的流,然後調用提供的完成處理塊.
注意:因為app必須實現URLSession:task:needNewBodyStream:方法,所以這種形式不支援使用系統預設的代理.
使用下載任務來上傳檔案
當下載任務建立時,app需要提供一個NSData對象或者一個流作為NSURLRequest對象的參數.
如果使用資料流,app需要實現 URLSession:task:needNewBodyStream: 方法來處理認證失敗的情況.詳細描述在通過流上傳資料
處理認證和安全傳輸確認
如果遠程伺服器返回一個狀態值表明需要進行認證或者認證需要特定的環境(例如一個SSL用戶端認證),NSURLSession調用會調用一個認證相關的代理方法.
- 會話層級:NSURLAuthenticationMethodNTLM, NSURLAuthenticationMethodNegotiate, NSURLAuthenticationMethodClientCertificate,或者 NSURLAuthenticationMethodServerTrust,會話對象調用會話代理方法URLSession:didReceiveChallenge:completionHandler: .如果app沒有提供會話代理,會話對象調用任務得代理方法URLSession:task:didReceiveChallenge:completionHandler:.
- 非會話層級:NSURLSession對象調用會話代理方法URLSession:task:didReceiveChallenge:completionHandler:.如果app提供了會話代理,而且app需要處理認證,那麼你必須在任務層級進行處理. 在非會話層級上,URLSession:didReceiveChallenge:completionHandler:不會被調用.
更多資訊參見 Authentication Challenges and TLS Chain Validation.
處理iOS後台活動
在iOS中使用NSURLSession,當一個下載任務完成時,app將會自動重啟.app代理方法application:handleEventsForBackgroundURLSession:completionHandler: 負責重建合適的會話,儲存完成處理器,並在會話對象調用會話代理的URLSessionDidFinishEventsForBackgroundURLSession: 方法時調用完成處理器.
清單1-8,清單1-9分別樣本了這些會話和app代理方法
清單1-8,iOS後台下載的會話代理方法
#if TARGET_OS_IPHONE-(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session{ NSLog(@"Background URL session %@ finished events.\n", session);if (session.configuration.identifier) [self callCompletionHandlerForSession: session.configuration.identifier];}- (void) addCompletionHandler: (CompletionHandlerType) handler forSession: (NSString *)identifier{if ([ self.completionHandlerDictionary objectForKey: identifier]) { NSLog(@"Error: Got multiple handlers for a single session identifier. This should not happen.\n");}[ self.completionHandlerDictionary setObject:handler forKey: identifier];}- (void) callCompletionHandlerForSession: (NSString *)identifier{CompletionHandlerType handler = [self.completionHandlerDictionary objectForKey: identifier];if (handler) { [self.completionHandlerDictionary removeObjectForKey: identifier]; NSLog(@"Calling completion handler.\n"); handler(); }}#endif
清單1-9,iOS後台下載的app代理方法
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler{ NSURLSessionConfiguration *backgroundConfigObject = [NSURLSessionConfiguration backgroundSessionConfiguration: identifier];NSURLSession *backgroundSession = [NSURLSession sessionWithConfiguration: backgroundConfigObject delegate: self.mySessionDelegate delegateQueue: [NSOperationQueue mainQueue]];NSLog(@"Rejoining session %@\n", identifier);[ self.mySessionDelegate addCompletionHandler: completionHandler forSession: identifier];}
轉載請註明出處:http://blog.csdn.net/qq329735967
任何疑問歡迎Email至[email protected]
apple官方文檔翻譯:使用NSURLSession(二)