URL加載系統之二:NSURLSession

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];

}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容