1. NSURLSessionDataTask 是 NSURLSessionTask 的子類,是一個具體的 網絡請求(task) 類,是網絡請求中最常用的請求之一
通常,NSURLSessionDataTask 用來請求數據,可以用來下載數據資源,例如 JSON 數據,圖片數據等
2. 通常有以下幾種方法創建一個 data task
1)方法一 : 使用 NSURLSession 的實例方法
1 // @param url 待請求的 URL 地址 2 // @param completionHandler 回調方法 3 // @param data 從服務器請求到的數據 4 // @param response 響應頭信息 5 // @param error 錯誤信息 6 - (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
注意 :
該方法中會自動將 url 轉換為一個請求對象(NSURLRequest),並且該請求對象是 GET 請求方式
回調方法是在子線程中運行的,所以如果在回調方法中刷新 UI 必須回到主線程中
回調方法中有兩個參數 response / error,這兩個參數和 該消息的接受者(即 NSURLSessionDataTask 對象)中的 response / error 是指同一個內容,即地址相同
使用該方法的缺點是不能監聽獲取數據的進度,因為只有當全部請求完數據後,才會調用這個方法,也就是說,data 中的數據是請求的全部數據
2)方法二 : 使用 NSURLSession 的實例方法
// @param request 請求對象 - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
方法二與方法一不同的地方在於 : 方法二可以手動設置請求對象,這樣一來,就可以指定請求方式 GET/POST 中的一個;而方法一只能是 GET 請求方式
剩余的全部一樣
3)方法三 : 代理
方法一和方法二唯一的缺點就是不能監控請求進度,因為只有當請求完全部的數據後才會調用回調方法,如果想要監控請求進度,必須使用代理的方法
使用代理首先要自定義 NSURLSession 對象,使用下面的方法可以設置代理對象
// @param configuration 配置信息對象 // @param delegate 代理對象 // @param queue 代理方法在哪個線程中運行,如果傳 nil 則會在子線程中運行代理方法 + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
同時,必須遵守相關的協議
在使用下面的方法創建 data task
- (NSURLSessionDataTask *)dataTaskWithURL:(NSURL *)url; - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
這兩個方法的區別和方法一/方法二一樣,使用 url 則方法內部會自動將其轉換為一個 請求對象,並且是 GET 請求方式
因為我是把所有的代碼寫到一個 demo 裡的,所以有些目前不需要的東西可以不必理會
1)GET/POST 請求
首先在 main.storyboard 中拖入一個 UINavigationController ,並設置 staticcell,如圖
然後拖入一個 UIViewController 並選中第一個 cell,即 NSURLSessionDataTask,將 cell 和 UIViewController 連線,選擇 push
在這個 UIViewController 中拖入一系列控件,如圖
右上角的 UIBarButtonItem 是跳轉到下一個界面的,不用管它
UIViewController 中的代碼如下
1 #import "LHDataTaskViewController.h" 2 3 // GET 請求的 URL 4 static NSString * imageURL = @"http://120.25.226.186:32812/resources/images/minion_01.png"; 5 6 // POST 請求的 URL 7 static NSString * dataURL = @"http://api.hudong.com/iphonexml.do"; 8 9 @interface LHDataTaskViewController () 10 11 #pragma mark - 屬性 12 @property (weak, nonatomic) IBOutlet UIImageView *showImageView; 13 14 @property (nonatomic, strong) NSURLSession * session; 15 @property (nonatomic, strong) NSURLSessionDataTask * dataTask; 16 17 @end 18 19 @implementation LHDataTaskViewController 20 21 #pragma mark - ViewController 生命周期 22 - (void)viewDidLoad { 23 24 [super viewDidLoad]; 25 26 // 1. 初始化 NSURLSession 對象 27 self.session = [NSURLSession sharedSession]; 28 29 } 30 31 - (void)didReceiveMemoryWarning { 32 33 [super didReceiveMemoryWarning]; 34 35 }
1. GET 請求
設置 "GET請求" 按鈕的 動作方法
1 #pragma mark - button 動作方法 2 #pragma mark 發送 GET 請求獲取圖片資源 3 - (IBAction)GETButtonClick:(id)sender { 4 5 NSLog(@"dataTask的狀態 --- %li", _dataTask.state); 6 7 // 1. 初始化 NSURLSesionDataTask 對象 8 self.dataTask = [_session dataTaskWithURL:[NSURL URLWithString:imageURL] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 9 10 // 1). 定義 UIImage 對象,並用接受的數據(data)初始化 11 UIImage * image = [UIImage imageWithData:data]; 12 13 // 2). 返回主線程刷新UI 14 dispatch_async(dispatch_get_main_queue(), ^{ 15 16 self.showImageView.image = image; 17 18 }); 19 20 NSLog(@"dataTask的狀態 --- %li", _dataTask.state); 21 22 // 此時,所有數據已經全部接受完畢,所以,已經接受的的數據和所要接受的總數據是相等的 23 // 因為沒有發送數據,所以發送數據都為 0 24 NSLog(@"已接受到的數據量 --- %lli", _dataTask.countOfBytesReceived); // 48347 25 NSLog(@"所要接受到的數據總量 --- %lli", _dataTask.countOfBytesExpectedToReceive); // 48347 26 NSLog(@"已經發送的數據量 --- %lli", _dataTask.countOfBytesSent); // 0 27 NSLog(@"所要發送的數據總量 --- %lli", _dataTask.countOfBytesExpectedToSend); // 0 28 29 }]; 30 31 // 2. 發送請求,執行 task 32 [_dataTask resume]; 33 34 }
其中,24行 —— 27行 中用到的屬性,在上一節已經介紹過
注意 : NSURLSessionTask 中所有的 task 都需要 resume 來開始
2. POST 請求
設置 "POST請求" 按鈕的動作方法
1 - (IBAction)POSTButtonClick:(UIButton *)sender { 2 3 // 1. 創建請求對象(可變) 4 NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:dataURL]]; 5 6 // 2. 設置請求方法為 POST 請求 7 request.HTTPMethod = @"POST"; 8 9 request.HTTPBody = [@"type=focus-c" dataUsingEncoding:NSUTF8StringEncoding]; 10 11 // 1. 初始化 NSURLSessionDataTask 對象 12 self.dataTask = [_session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 13 14 NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); 15 16 }]; 17 18 [_dataTask resume]; 19 20 }
2. 代理
使用代理的方法來進行網絡請求,並且可以監控請求進度
現在 mian.storyboard 中拖入一個 UIViewController 並添加控件,如圖
本次請求的是一張圖片數據,請求完之後會將圖片顯示到屏幕上的 UIImageView,resume 按鈕是開始請求,pause 按鈕是暫停請求,cance 按鈕是取消請求,中間還有一個 UIProgressView,用於顯示請求的進度,並將這些控件與插座變量關聯
UIViewController 中的代碼如下,該 ViewController 要遵守相關協議 <NSURLSessionDataDelegate>
1 #import "LHDataTaskDownloadViewController.h" 2 3 // 待訪問的 URL 4 static NSString * imageURL = @"http://f12.topit.me/o129/10129120625790e866.jpg"; 5 6 @interface LHDataTaskDownloadViewController () <NSURLSessionDataDelegate> 7 8 #pragma mark - 屬性列表 9 #pragma mark 插座變量 10 @property (weak, nonatomic) IBOutlet UIImageView *showImageView; 11 @property (weak, nonatomic) IBOutlet UIProgressView *progressView; 12 @property (weak, nonatomic) IBOutlet UIButton *resumeButton; 13 @property (weak, nonatomic) IBOutlet UIButton *pauseButton; 14 @property (weak, nonatomic) IBOutlet UIButton *cancelButton; 15 16 #pragma mark 網絡對象 17 @property (nonatomic, strong) NSURLSession * session; 18 @property (nonatomic, strong) NSURLSessionDataTask * dataTask; 19 20 #pragma mark 用於接受數據的對象 21 @property (nonatomic, strong) NSMutableData * data; 22 23 @end 24 25 @implementation LHDataTaskDownloadViewController 26 27 - (void)viewDidLoad { 28 29 [super viewDidLoad]; 30 31 // 1. 初始化 NSURLSession 對象,delegateQueue 為協議方法運行的線程,傳 nil 則在子線程中運行 32 self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil]; 33 34 // 2. 初始化 NSURLSessionDataTask 對象,默認為 GET 35 self.dataTask = [_session dataTaskWithURL:[NSURL URLWithString:imageURL]]; 36 37 // 2. 將 cancelButton 和 pauseButton 按鈕設置為不可用 38 _cancelButton.enabled = NO; 39 40 _pauseButton.enabled = NO; 41 42 }
開始按鈕的動作方法
1 - (IBAction)resumeButtonClick:(id)sender { 2 3 // 1. 判斷當前的狀態,data task 默認為 暫停狀態 4 if (_dataTask.state == NSURLSessionTaskStateSuspended) { 5 6 _pauseButton.enabled = YES; 7 8 _cancelButton.enabled = YES; 9 10 // 1). 開始請求 task 11 [_dataTask resume]; 12 13 } 14 15 }
暫停按鈕的動作方法
1 - (IBAction)pauseButtonClick:(id)sender { 2 3 // 1. 判斷 task 當前的狀態,如果處於正在接受數據的狀態,則暫停 4 if (_dataTask.state == NSURLSessionTaskStateRunning) { 5 6 [_dataTask suspend]; 7 8 } 9 10 }
取消按鈕的動作方法
1 - (IBAction)cancelButtonClick:(id)sender { 2 3 // 1. 判斷 task 當前的狀態,如果處於正在接受數據的狀態或暫停狀態,則取消 4 if (_dataTask.state == NSURLSessionTaskStateRunning || _dataTask.state == NSURLSessionTaskStateSuspended) { 5 6 // 1). 取消 task 7 [_dataTask cancel]; 8 9 // 2). 設置滑動條的值 10 _progressView.progress = 0; 11 12 // 3). 創建對話框VC 13 UIAlertController * alertVC = [UIAlertController alertControllerWithTitle:@"提示" message:@"該 Task 已經被取消" preferredStyle:UIAlertControllerStyleAlert]; 14 15 // 4). 顯示對話框VC 16 [self presentViewController:alertVC animated:YES completion:nil]; 17 18 // 5). 創建動作按鈕 19 UIAlertAction * cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { 20 21 [self.navigationController popViewControllerAnimated:YES]; 22 23 }]; 24 25 // 6). 將動作按鈕添加到對話框VC 26 [alertVC addAction:cancelAction]; 27 28 } 29 30 }
協議方法
#pragma mark 接收到服務器響應時調用,默認情況下不接受數據,所以要允許 // @param session 當前的會話對象 // @param dataTask 當前的 data task 對象 // @param response 響應頭對象 // @param completionHandler 回調 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { NSLog(@"%@", NSStringFromSelector(_cmd)); // 1. 初始化數據對象 self.data = [[NSMutableData alloc] init]; // 2. 允許接受數據,如果沒有寫這句,則後面代理的方法不會被執行 completionHandler(NSURLSessionResponseAllow); }
其中,NSURLSessionResponseDisposition 是一個枚舉
1 typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) { 2 NSURLSessionResponseCancel = 0, // 取消接受數據,之後的代理方法不會被執行,相當於 [task cancel]; 3 NSURLSessionResponseAllow = 1, // 允許接受數據,之後的代理方法會被執行 4 NSURLSessionResponseBecomeDownload = 2,// 使當前的 data task 變為 download task,當轉換為 download task 時,會將數據下載到 tmp 文件中,不需要再接受數據了,並且必須調用下面的 iii) 方法,並且在該方法中可以什麼都不寫,但必須被調用 5 NSURLSessionResponseBecomeStream NS_ENUM_AVAILABLE(10_11, 9_0) = 3,// 使當前的 data task 變為 stream task 6 } NS_ENUM_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0);
1 #pragma mark 接受到數據時調用,可能會調用多次 2 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { 3 4 NSLog(@"%@", NSStringFromSelector(_cmd)); 5 6 // 1. 拼接收到的數據 7 [self.data appendData:data]; 8 9 // 2. 回到主線程刷新 UI 10 dispatch_async(dispatch_get_main_queue(), ^{ 11 12 _progressView.progress = (float)_dataTask.countOfBytesReceived/_dataTask.countOfBytesExpectedToReceive; 13 14 }); 15 16 }
1 #pragma mark 請求結束或失敗時調用 2 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { 3 4 NSLog(@"%@", NSStringFromSelector(_cmd)); 5 6 UIImage * image = [UIImage imageWithData:_data]; 7 8 dispatch_async(dispatch_get_main_queue(), ^{ 9 10 NSLog(@"currentThread --- %@", [NSThread currentThread]); 11 12 self.showImageView.image = image; 13 14 }); 15 16 }
這個方法並不是 NSURLSessionTaskDelegate 協議中的方法,適合所有的 task