软件设计 2017-05-02
1. NSURLSessionDataTask 是 NSURLSessionTask 的子类,是一个具体的 网络请求(task) 类,是网络请求中最常用的请求之一
通常,NSURLSessionDataTask 用来请求数据,可以用来下载数据资源,例如 JSON 数据,图片数据等
2. 通常有以下几种方法创建一个 data task
1)方法一 : 使用 NSURLSession 的实例方法
// @param url 待请求的 URL 地址 // @param completionHandler 回调方法 // @param data 从服务器请求到的数据 // @param response 响应头信息 // @param error 错误信息 - (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 中的代码如下
#import "LHDataTaskViewController.h" // GET 请求的 URL static NSString * imageURL = @"http://120.25.226.186:32812/resources/images/minion_01.png"; // POST 请求的 URL static NSString * dataURL = @"http://api.hudong.com/iphonexml.do"; @interface LHDataTaskViewController () #pragma mark - 属性 @property (weak, nonatomic) IBOutlet UIImageView *showImageView; @property (nonatomic, strong) NSURLSession * session; @property (nonatomic, strong) NSURLSessionDataTask * dataTask; @end @implementation LHDataTaskViewController #pragma mark - ViewController 生命周期 - (void)viewDidLoad { [super viewDidLoad]; // 1. 初始化 NSURLSession 对象 self.session = [NSURLSession sharedSession]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; }
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请求" 按钮的动作方法
- (IBAction)POSTButtonClick:(UIButton *)sender { // 1. 创建请求对象(可变) NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:dataURL]]; // 2. 设置请求方法为 POST 请求 request.HTTPMethod = @"POST"; request.HTTPBody = [@"type=focus-c" dataUsingEncoding:NSUTF8StringEncoding]; // 1. 初始化 NSURLSessionDataTask 对象 self.dataTask = [_session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); }]; [_dataTask resume]; }
2. 代理
使用代理的方法来进行网络请求,并且可以监控请求进度
现在 mian.storyboard 中拖入一个 UIViewController 并添加控件,如图
本次请求的是一张图片数据,请求完之后会将图片显示到屏幕上的 UIImageView,resume 按钮是开始请求,pause 按钮是暂停请求,cance 按钮是取消请求,中间还有一个 UIProgressView,用于显示请求的进度,并将这些控件与插座变量关联
UIViewController 中的代码如下,该 ViewController 要遵守相关协议 <NSURLSessionDataDelegate>
#import "LHDataTaskDownloadViewController.h" // 待访问的 URL static NSString * imageURL = @"http://f12.topit.me/o129/10129120625790e866.jpg"; @interface LHDataTaskDownloadViewController () <NSURLSessionDataDelegate> #pragma mark - 属性列表 #pragma mark 插座变量 @property (weak, nonatomic) IBOutlet UIImageView *showImageView; @property (weak, nonatomic) IBOutlet UIProgressView *progressView; @property (weak, nonatomic) IBOutlet UIButton *resumeButton; @property (weak, nonatomic) IBOutlet UIButton *pauseButton; @property (weak, nonatomic) IBOutlet UIButton *cancelButton; #pragma mark 网络对象 @property (nonatomic, strong) NSURLSession * session; @property (nonatomic, strong) NSURLSessionDataTask * dataTask; #pragma mark 用于接受数据的对象 @property (nonatomic, strong) NSMutableData * data; @end @implementation LHDataTaskDownloadViewController - (void)viewDidLoad { [super viewDidLoad]; // 1. 初始化 NSURLSession 对象,delegateQueue 为协议方法运行的线程,传 nil 则在子线程中运行 self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil]; // 2. 初始化 NSURLSessionDataTask 对象,默认为 GET self.dataTask = [_session dataTaskWithURL:[NSURL URLWithString:imageURL]]; // 2. 将 cancelButton 和 pauseButton 按钮设置为不可用 _cancelButton.enabled = NO; _pauseButton.enabled = NO; }
开始按钮的动作方法
- (IBAction)resumeButtonClick:(id)sender { // 1. 判断当前的状态,data task 默认为 暂停状态 if (_dataTask.state == NSURLSessionTaskStateSuspended) { _pauseButton.enabled = YES; _cancelButton.enabled = YES; // 1). 开始请求 task [_dataTask resume]; } }
暂停按钮的动作方法
- (IBAction)pauseButtonClick:(id)sender { // 1. 判断 task 当前的状态,如果处于正在接受数据的状态,则暂停 if (_dataTask.state == NSURLSessionTaskStateRunning) { [_dataTask suspend]; } }
取消按钮的动作方法
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);
#pragma mark 接受到数据时调用,可能会调用多次 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { NSLog(@"%@", NSStringFromSelector(_cmd)); // 1. 拼接收到的数据 [self.data appendData:data]; // 2. 回到主线程刷新 UI dispatch_async(dispatch_get_main_queue(), ^{ _progressView.progress = (float)_dataTask.countOfBytesReceived/_dataTask.countOfBytesExpectedToReceive; }); }
#pragma mark 请求结束或失败时调用 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { NSLog(@"%@", NSStringFromSelector(_cmd)); UIImage * image = [UIImage imageWithData:_data]; dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"currentThread --- %@", [NSThread currentThread]); self.showImageView.image = image; }); }
这个方法并不是 NSURLSessionTaskDelegate 协议中的方法,适合所有的 task