NSURLSession及相關的類提供通過HTTP協議下載數據的API。該類提供了大量代理方法來支持認證和后臺下載(程序未運行或掛起時)功能。
為了使用NSURLSession,我們的應用會創建一系列的會話,每個會話負責協調一組相關數據的傳輸任務。在每個會話中,我們的應用添加一系列的任務,每個任務都表示一個對指定URL的請求。與大多數網絡API一樣,NSURLSession API是異步的。如果我們使用系統提供的代理,我們必須提供一個請求完成處理block,以便在請求成功或失敗時返回數據給我們的應用。如果我們提供自定義的代理對象,則任務對象調用這些代理方法,并回傳從服務端獲取的數據(如果是文件下載,則當傳輸完成時調用)。
NSURLSession提供了status和progress屬性,并作為額外的信息傳遞給代理。同時它支持取消、恢復、掛起操作,并支持斷點續傳功能。
要掌握NSURLSession的使用,我們需要了解下URL會話的一些內容
URL會話
在一個會話中的任務的行為取決于三個方面:
session的類型(由創建會話時的配置對象確定)
任務的類型
當任務創建時應用是否在前臺
NSURLSession支持以下三種會話類型:
默認會話:行為與其它下載URL的Foundation方法類似。使用基于磁盤的緩存策略,并在用戶keychain中存儲證書。
短暫會話(Ephemeral sessions):不存儲任何數據在磁盤中;所有的緩存,證書存儲等都保存在RAM中并與會話綁定。這樣,當應用結束會話時,它們被自動釋放。
后臺會話(Background sessions):類似于默認會話,除了有一個獨立的進程來處理所有的數據傳輸。
在一個會話中,NSURLSession支持三種任務類型
數據任務:使用NSData對象來發送和接收數據。數據任務可以分片返回數據,也可以通過完成處理器一次性返回數據。由于數據任務不存儲數據到文件,所以不支持后臺會話.
下載任務:以文件的形式接收數據,當程序不運行時支持后臺下載
上傳任務:通常以文件的形式發送數據,支持后臺上傳。
NSURLSession支持在程序掛起時在后臺傳輸數據。后臺傳輸只由使用后臺會話配置對象創建的會話提供。使用后臺會話時,由于實際傳輸是在一個獨立的進程中傳輸,且重啟應用進程相當損耗資源,只有少量特性可以使用,所以有以下限制:
會話必須提供事件分發的代理。
只支持HTTP和HTTPS協議
只支持上傳和下載任務
總是伴隨著重定義操作
如果當應用在后臺時初始化的后臺傳輸,則配置對象的discretionary屬性為true
在iOS中,當我們的應用不再運行時,如果后臺下載任務完成或者需要證書,則系統會在后臺自動重啟我們的應用,同時調用UIApplicationDelegate對象的application:handlerEventsForBackgroundURLSession:completionHandler:方法。這個調用會提供啟動的應用的session的標識。我們的應用應該存儲完成處理器,使用相同的標識來創建后臺配置對象,然后使用配置對象來創建會話。新的會話會與運行的后臺activity關聯。當會話完成后臺下載任務時,會給會話代理發送一個URLSessioinDidFinishEventsForBackgroundURLSession:消息。我們的代理對象然后調用存儲的完成處理器。
如果在程序掛起時有任何任務完成,則會調用URLSession:downloadTask:didFinishDownloadingToURL:方法。同樣的,如果任務需要證書,則NSURLSession對象會在適當的時候調用URLSession:task:didReceiveChallenge:completionHandler: 和URLSession:didReceiveChallenge:completionHandler:方法。
這里需要注意的是必須為每個標識創建一個會話,共享相同標識的多個會話的行為是未定義的。
會話和任務對象實現了NSCopying協議:
當應用拷貝一個會話或任務對象時,會獲取相同對象的指針
當應用拷貝一個配置對象時,會獲取一個可單獨修改的新的對象
創建并配置NSURLSession
我們下面舉個簡單的實例來說明一個NSURLSession與服務端的數據交互。
代碼清單1:聲明三種類型會話對象
@interface URLSession : NSObject
@property (nonatomic, strong) NSURLSession *backgroundSession;
@property (nonatomic, strong) NSURLSession *defaultSession;
@property (nonatomic, strong) NSURLSession *ephemeralSession;
@property (nonatomic, strong) NSMutableDictionary *completionHandlerDictionary;
- (void)addCompletionHandler:(CompletionHandlerType)handler forSession:(NSString *)identifier;
- (void)callCompletionHandlerForSession:(NSString *)identifier;
@end
NSURLSession提供了大量的配置選項,包括:
支持緩存、cookie,認證及協議的私有存儲
認證
上傳下載文件
每個主機的配置最大數
超時時間
支持的TLS最小最小版本
自定義代理字典
控制cookie策略
控制HTTP管道行為
由于大部分設置都包含在一個獨立的配置對象中,所以我們可以重用這些配置。當我們初始一個會話對象時,我們指定了如下內容
一個配置對象,用于管理其中的會話和任務的行為
一個代理對象,用于在收到數據時處理輸入數據,及會話和任務中的其它事件,如服務端認證、確定一個資源加載請求是否應該轉換成下載等。這個對象是可選的。但如果我們需要執行后臺傳輸,則必須提供自定義代理。
在實例一個會話對象后,我們不能改變改變配置或代理。
代碼清單2演示了如何創建一個會話
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
// 配置會話的緩存
NSString *cachePath = @"/MyCacheDirectory";
NSArray *pathList = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *path = [pathList objectAtIndex:0];
NSString *bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
NSString *fullCachePath = [[path stringByAppendingPathComponent:bundleIdentifier] stringByAppendingPathComponent:cachePath];
NSLog(@"Cache path: %@", fullCachePath);
NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:16384 diskCapacity:268435456 diskPath:cachePath];
defaultConfigObject.URLCache = cache;
defaultConfigObject.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
self.defaultSession = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:[NSOperationQueue mainQueue]];
除了后臺配置對象外,我們可以重用會話的配置對象來創建新的會話,正如上面所講的,拷貝一個配置對象會生成一個新的獨立的配置對象。我們可以在任何時候安全的修改配置對象。當創建一個會話時,會話會對配置對象進行深拷貝,所以修改只會影響到新的會話。代理清單3演示了創建一個新的會話,這個會話使用重用的配置對象。
代碼清單3:重用會話對象
self.ephemeralSession = [NSURLSession sessionWithConfiguration:ephemeralConfigObject delegate:self delegateQueue:[NSOperationQueue mainQueue]];
ephemeralConfigObject.allowsCellularAccess = YES;
// ...
NSURLSession *ephemeralSessionWifiOnly = [NSURLSession sessionWithConfiguration:ephemeralConfigObject delegate:self delegateQueue:[NSOperationQueue mainQueue]];
使用NSURLSession獲取數據基本就是兩步:
創建一個配置對象及基于這個對象的會話
定義一個請求完成處理器來處理獲取到的數據。
如果使用系統提供的代理,只需要代碼清單4這幾行代碼即可搞定
代碼清單4:使用系統提供代理
NSURLSession *delegateFreeSession = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:[NSOperationQueue mainQueue]];
[delegateFreeSession dataTaskWithRequest:@"http://www.sina.com"
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"Got response %@", response);
}];
只是系統提供的代理只提供有限的網絡行為。如果應用需要更多的處理,如自定義認證或后臺下載等,則需要使用自定義的代理。使用自定義代理來獲取數據時,代理必須實現以下方法:
URLSession:dataTask:didReceiveData: 從請求提供數據給我們的任務,一次一個數據塊
URLSession:task:didCompleteWithError: 表示任務已經接受了所有的數據。
如果我們在URLSession:dataTask:didReceiveData:方法返回后使用數據,則需要將數據存儲在某個地方。
代碼清單5:演示了一個數據訪問實例:
NSURL *url = [NSURL URLWithString:@"http://www.sina.com"];
NSURLSessionDataTask *dataTask = [self.defaultSession dataTaskWithURL:url];
[dataTask resume];
如果遠程服務器返回的狀態表示需要一個認證,且認證需要連接級別的處理時,NSURLSession將調用認證相關代理方法。這個具體我們后面文章將詳細討論。
處理iOS后臺Activity
在iOS中使用NSURLSession時,當一個下載完成時,會自動啟動我們的應用。應用的代理方法application:handleEventsForBackgroundURLSession:completionHandler: 負責創建一個合適的會話,存儲請求完成處理器,并在會話調用會話代理的URLSessionDidFinishEventsForBackgroundURLSession: 方法時調用這個處理器。代碼清單6與代碼清單7演示了這個處理流程
代碼清單6:iOS后臺下載的會話代理方法
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
NSLog(@"background url session %@", session);
if (session.configuration.identifier)
{
[self callCompletionHandlerForSession:session.configuration.identifier];
}
}
- (void)callCompletionHandlerForSession:(NSString *)identifier
{
CompletionHandlerType handler = [self.completionHandlerDictionary objectForKey:identifier];
if (handler) {
[self.completionHandlerDictionary removeObjectForKey:identifier];
handler();
}
}
代碼清單7:iOS后臺下載的App 代理方法
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler
{
NSURLSessionConfiguration *backgroundConfigObject = [NSURLSessionConfiguration backgroundSessionConfiguration:identifier];
URLSession *sessionDelegate = [[URLSession alloc] init];
NSURLSession *backgroundSession = [NSURLSession sessionWithConfiguration:backgroundConfigObject
delegate:sessionDelegate
delegateQueue:[NSOperationQueue mainQueue]];
[sessionDelegate addCompletionHandler:completionHandler forSession:identifier];
}