iOS —— NNSURLSessionDataTask,nsurlsessiondatatask
一、基本簡介
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 ,並設定 static cell,
然後拖入一個 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 @end18 19 @implementation LHDataTaskViewController20 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). 返回主線程重新整理UI14 dispatch_async(dispatch_get_main_queue(), ^{15 16 self.showImageView.image = image;17 18 });19 20 NSLog(@"dataTask的狀態 --- %li", _dataTask.state);21 22 // 此時,所有資料已經全部接受完畢,所以,已經接受的的資料和所要接受的總資料是相等的23 // 因為沒有發送資料,所以發送資料都為 024 NSLog(@"已接受到的資料量 --- %lli", _dataTask.countOfBytesReceived); // 4834725 NSLog(@"所要接受到的資料總量 --- %lli", _dataTask.countOfBytesExpectedToReceive); // 4834726 NSLog(@"已經發送的資料量 --- %lli", _dataTask.countOfBytesSent); // 027 NSLog(@"所要發送的資料總量 --- %lli", _dataTask.countOfBytesExpectedToSend); // 028 29 }];30 31 // 2. 發送請求,執行 task32 [_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 @end24 25 @implementation LHDataTaskDownloadViewController26 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 對象,預設為 GET35 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). 開始請求 task11 [_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). 建立對話方塊VC13 UIAlertController * alertVC = [UIAlertController alertControllerWithTitle:@"提示" message:@"該 Task 已經被取消" preferredStyle:UIAlertControllerStyleAlert];14 15 // 4). 顯示對話方塊VC16 [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). 將動作按鈕添加到對話方塊VC26 [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 task6 } 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. 回到主線程重新整理 UI10 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