一. 背景
NSURLSession是蘋果在iOS7后推出的通過HTTP協議下載數據的API,該類提供了大量的代理方法,來支持認證和后臺下載功能。
使用NSURLSession須知:
a> 我們的應用會創建一系列的對話,每個對話負責一組的數據傳輸任務,并且會添加一系列的任務,每個任務代表著一個URL請求。
b> NSURLSession的API是異步的。
c> NSURLSession的使用分成:1.系統代理的方式(需要提供一個請求完成后處理的block)2. 自定義代理的方式(執行代理方法來處理請求成功或失敗)
d> NSURLSession支持取消、恢復、掛起操作,以及斷掉續傳的功能
二. NSURLSession
NSURLSession根據配置對象(NSURLSessionConfiguration)分為三大類型
- 默認會話(defaultSessionConfiguration):存儲cookie(緩存)在磁盤中,存儲證書在用戶keychain
- 后臺會話(backgroundSessionConfiguration):存儲cookie(緩存)在磁盤中,存儲證書在用戶keychain。后臺會添加獨立的進程來處理數據傳輸任務
- 短暫會話(ephemeralSessionConfiguration):緩存和證書都存在RAM中,會話結束,它們就自動釋放
任務類型也分為三大類
- dataTask:使用NSData對象進行上傳和下載數據;數據可一次性返回,也可以分片段返回;返回的數據不是進行文件存儲,所以不支持后臺會話(iOS8后支持 備注:主要用于一些小數據請求,或大文件的斷點續傳下載
- downloadTask:以文件的形式接收數據,當程序不運行時支持后臺下載
- uploadTask:以文件的形式上傳數據,支持后臺下載
簡單使用
1. 系統代理的方式
a>. dataTask
// 1.創建NSURLSession單例對象
NSURLSession *session = [NSURLSession sharedSession];
// 2.添加dataTask任務(1>. 通過NSURLRequest方式 ;2>. 通過NSURL方式(內部進行封裝成NSURLRequest對象))
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://xxx"]];
/*
POST請求請加這兩句
request.HTTPMethod = @"POST";
request.HTTPBody = [@"參數" dataUsingEncoding:NSUTF8StringEncoding];
*/
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
/*
data:請求成功后返回的數據
response:請求頭
error:如果有值說明請求失敗
*/
}];
//或則
// NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"http://xxx"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//
// }];
// 3.開始任務
[dataTask resume];
b>. downLoadTask
內部已經實現邊下載邊寫入temp文件中,由于temp中的文件易清除,所以須手動將文件剪切到合適的沙河目錄 缺點:無法監控下載進度
// 1.創建NSURLSession單例對象
NSURLSession *session = [NSURLSession sharedSession];
// 2.添加downloadTask任務(1>. 通過NSURLRequest方式 ;2>. 通過NSURL方式(內部進行封裝成NSURLRequest對象))
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://xxx"]];
/*
POST請求請加這兩句
request.HTTPMethod = @"POST";
request.HTTPBody = [@"參數" dataUsingEncoding:NSUTF8StringEncoding];
*/
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
/*
location:下載的文件的保存地址,默認是temp
*/
}];
//或則
// NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:[NSURL URLWithString:@"http://xxx"] completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//
// }];
// 3.開始任務
[downloadTask resume];
c>. upLoadTask
// 1.創建NSURLSession單例對象
NSURLSession *session = [NSURLSession sharedSession];
// 2.添加uploadTask任務
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://xxx"]];
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromFile:[NSURL URLWithString:@"daf.af.af.3422"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
// 3.開始任務
[uploadTask resume];
2.通過設置配置實現代理方法的方式
a>. dataTask下載文件
// 1.創建默認配置的NSURLSession對象
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
/*
NSURLSessionConfiguration:session對象的全局配置設置,一般使用默認配置就可以
delegate:設置代理
delegateQueue:代理方法在哪個隊列中執行(在哪個線程中調用),如果是主隊列那么在主線程中執行,如果是非主隊列,那么在子線程中執行
*/
// 2.創建一個Task, 請求方法為get和post均可
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:@"http://xxxx"]];
// 3.執行任務(可暫停、取消、恢復等)
[dataTask resume]; // [dataTask suspend] [dataTask cancel];
// 4. 遵守代理協議,實現代理方法(3個相關的代理方法)
/* 1.當接收到服務器響應的時候調用 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
// 通過該block回調,告訴服務器端是否接收返回的數據
completionHandler(NSURLSessionResponseAllow);
/*
NSURLSessionResponseCancel = 0, 取消任務
NSURLSessionResponseAllow = 1, 接收任務
NSURLSessionResponseBecomeDownload = 2, 轉變成下載
NSURLSessionResponseBecomeStream NS_ENUM_AVAILABLE(10_11, 9_0) = 3, 轉變成流
*/
}
/* 2.當接收到服務器返回的數據時調用 該方法可能會被調用多次 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
}
/* 3.當請求完成之后調用該方法 不論是請求成功還是請求失敗都調用該方法,如果請求失敗,那么error對象有值,否則那么error對象為空 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
}
b>. downloadTask下載文件
和dataTask一樣只是代理方法有所區別
// 4. 遵守代理協議,實現代理方法(3個相關的代理方法)
/* 1.可以在該方法中監聽文件下載的進度,該方法會被調用多次
*/
-(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
}
/* 2.恢復下載的時候調用該方法 */
-(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
}
/* 3.下載完成之后調用該方法 */
-(void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location {
}
/* 4.請求完成之后調用如果請求失敗,那么error有值 */
-(void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error {
}
注意:
- 如果通過[downloadTask cancel]取消任務,那么任務不能恢復了
- 通過以下方法暫停,那么該方法會以resumeData保存當前文件的已經下載數據,任務可以恢復下載
[self.downloadTask cancelByProducingResumeData:^(NSData * __nullable resumeData) {
self.resumeData = resumeData;
}];
恢復下載方法
self.downloadTask=[self.session downloadTaskWithResumeData:self.resumeData];
[self.downloadTask resume];
- 缺點
01 如果用戶點擊暫停之后退出程序,那么需要把恢復下載的數據寫一份到沙盒,代碼復雜度更
02 如果用戶在下載中途未保存恢復下載數據即退出程序,則不具備可操作性
c>. uploadTask上傳文件
//1.創建session
self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
//2.創建task
//2.1 創建請求對象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://xxxxx"]];
//2.2 設置請求方法
request.HTTPMethod = @"POST";
//2.3.設置請求頭
NSString *header = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",str];
[request setValue:header forHTTPHeaderField:@"Content-Type"];
//2.4設置文件上傳的文件內容
NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithRequest:request fromData:data];
[uploadTask resume];
// 4. 遵守代理協議,實現代理方法
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
}
后臺會話
當我們的運用退出,如果后臺下載任務完成或則需要證書時,后臺自動重啟我們的運用,同時調用UIApplicationDelegate對象application:handlerEventsForBackgroundURLSession:completionHandler:方法,這個方法會提供session的標識,然后我們要利用session的標識創建后臺配置,繼而創建新的后臺會話,與后臺的activity關聯。當后臺下載任務完成時,會調用后臺session的代理方法URLSessioinDidFinishEventsForBackgroundURLSession:然后調用存儲的完成處理
如果在程序掛起時有任何任務完成,則會調用URLSession:downloadTask:didFinishDownloadingToURL:方法。同樣的,如果任務需要證書,則NSURLSession對象會在適當的時候調用URLSession:task:didReceiveChallenge:completionHandler: 和URLSession:didReceiveChallenge:completionHandler:方法。
(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];
}
- (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();
}
}
注意:
NSURLSession對象的釋放
-(void)dealloc {
//在最后的時候應該把session釋放,以免造成內存泄露
// NSURLSession設置過代理后,需要在最后(比如控制器銷毀的時候)調用session的invalidateAndCancel或者resetWithCompletionHandler,才不會有內存泄露
// [self.session invalidateAndCancel];
[self.session resetWithCompletionHandler:^{
NSLog(@"釋放---");
}];