iOS NSURLSession
1 NSURLSession使用须知
NSURLSession的API是易步德
NSURLSession的使用分成系统代理方式和自定义方式
NSURLSession支持取消,恢复,挂起操作以及断掉续传的功能
NSURLSession的任务类型包括dataTask(以NSData对象进行上传和下载数据),downloadTask(以文件形式下载数据),uploadTask(以文件形式上传数据)
1 NSURLSession使用的步骤
//1 请求的url NSURL *url = [NSURL URLWithString:@"www.baidu.com/news/hfu3284"]; //2用url生成Request NSURLRequest *request = [NSURLRequest requestWithURL:url]; //3 生成session,将request作为参数,调用session的dataWithRwquest来方式请求 NSURLSession *session = [NSURLSession sharedSession]; [[session dataTaskWithRequest:request] resume];
2 http请求
//1、请求字符串 NSString *str = @"http://swiftcafe.io/2015/12/20/nsurlsession/"; //2、转化成url NSURL *url = [NSURL URLWithString:[str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]; //3、构建request NSURLRequest *request = [NSURLRequest requestWithURL:url];
//4、获取session NSURLSession *session = [NSURLSession sharedSession]; //5、用session发送请求,返回一个dataTask NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (data && (error == nil)) { NSLog(@"data=%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); } else {
NSLog(@"error=%@",error); } }]; //6、dataTask创建后是挂起状态,需要调用resume开启访问 [dataTask resume];
3 文件下载
先创建一个NSURLSession类,再创建一个下载任务类NSURLSessionDownloadTask类,将session加入到下载任务中,最后开始下载任务.
下载任务开启后,NSURLSessionDownLoadTask默认会将数据写入本地沙河存储文件(tmp)中,所以我们需要在临时文件下载之后,即在NSURLSessionDownTask的completionHandler这个block中将临时文件剪切到一个永久的文件地址保存起来// 1、请求字符串NSString *str = [NSString stringWithFormat:@"http://swiftcafe.io/2015/12/20/nsurlsession/894.png"];
//2、转化成url NSURL *url = [NSURL URLWithString:[str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]; //3、构建request NSURLRequest *request = [NSURLRequest requestWithURL:url]; //4、获取session NSURLSession *session = [NSURLSession sharedSession]; //5、用session发送请求,返回一个downloadTask NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) { // 文件将要移动到的指定目录 NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; // 新文件路径 NSString *newFilePath = [documentsPath stringByAppendingPathComponent:response.suggestedFilename];
// 移动文件到新路径 [[NSFileManager defaultManager] moveItemAtPath:location.path toPath:newFilePath error:nil]; }]; //6、downloadTask创建后是挂起状态,需要调用resume开启访问 [downloadTask resume];
NSURLSession实现断点下载(不支持离线下载)
NSURLSession实现断点下载的步骤:(1)设置一个downloadTask、session以及resumeData的全局变量; (2)如果开始下载,就创建一个新的downloadTask,并启动下载; (3)如果暂停下载,调用取消下载的函数,并在block中保存本次的resumeData到全局resumeData中;(4)如果恢复下载,将上次保存的resumeData加入到任务中,并启动下载。
定义下载文件需要用到的类和要实现的代理
@interface ViewController () <NSURLSessionDownloadDelegate> /** 下载进度条 */ @property (weak, nonatomic) IBOutlet UIProgressView *progressView; /** 下载进度条Label */ @property (weak, nonatomic) IBOutlet UILabel *progressLabel; /** NSURLSession断点下载(不支持离线)需用到的属性 **********/
/** 下载任务 */ @property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask; /** 保存上次的下载信息 */ @property (nonatomic, strong) NSData *resumeData; /** session */ @property (nonatomic, strong) NSURLSession *session; @end
实现下面的按钮点击代码,其中用到了session的懒加载。
/** * 点击按钮 -- 使用NSURLSession断点下载(不支持离线) */ - (IBAction)resumeDownloadBtnClicked:(UIButton *)sender { // 按钮状态取反 sender.selected = !sender.isSelected; if (nil == self.downloadTask) { // [开始下载/继续下载] if (self.resumeData) { // [继续下载] // 传入上次暂停下载返回的数据,就可以恢复下载 self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData]; // 开始任务 [self.downloadTask resume]; self.resumeData = nil; }else{ // [开始下载]:从0开始下载 NSURL* url = [NSURL URLWithString:@"http://dldir1.qq.com/qqfile/QQforMac/QQ_V5.4.0.dmg"]; // 创建任务 self.downloadTask = [self.session downloadTaskWithURL:url]; // 开始任务 [self.downloadTask resume]; } }else{ // [暂停下载] __weak typeof(self) weakSelf = self; [self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) { // resumeData:包含了继续下载的位置\下载的路径 weakSelf.resumeData = resumeData; weakSelf.downloadTask = nil; }]; } }
NSURLSession实现断点下载(支持离线下载) :用NSURLSessionDataTask来实现NSURLSession的离线断点下载。
NSURLSessionDataTask在发送请求之后,能够将返回的数据,作为data一部分一部分的接受过来。这样,我们就可以像NSURLConnection上边那样,创建一个NSFilehandle(文件句柄)类,在接受数据的时候,一点点写入永久沙盒文件中。并且在下次开始的时候,设置好HTTP请求头的Rang。我们就可以实现离线断点下载了。
定义下载文件需要用到的类和要实现的代理
@interface ViewController () <NSURLSessionDataDelegate> /** 下载进度条 */ @property (weak, nonatomic) IBOutlet UIProgressView *progressView; /** 下载进度条Label */ @property (weak, nonatomic) IBOutlet UILabel *progressLabel; /** NSURLSession断点下载(支持离线)需用到的属性 **********/ /** 文件的总长度 */ @property (nonatomic, assign) NSInteger fileLength; /** 当前下载长度 */ @property (nonatomic, assign) NSInteger currentLength; /** 文件句柄对象 */ @property (nonatomic, strong) NSFileHandle *fileHandle; /** 下载任务 */ @property (nonatomic, strong) NSURLSessionDataTask *downloadTask; /** session */ @property (nonatomic, strong) NSURLSession *session; @end
添加支持断点下载的[开始下载/暂停下载]按钮,并实现相应功能的代码
/** session的懒加载 */ - (NSURLSession *)session { if (!_session) { _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]]; } return _session; } /** downloadTask的懒加载,这里设置请求头中的Range */ - (NSURLSessionDataTask *)downloadTask { if (!_downloadTask) { // 创建下载URL NSURL *url = [NSURL URLWithString:@"http://dldir1.qq.com/qqfile/QQforMac/QQ_V5.4.0.dmg"]; // 2.创建request请求 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; // 设置HTTP请求头中的Range NSString *range = [NSString stringWithFormat:@"bytes=%zd-", self.currentLength]; [request setValue:range forHTTPHeaderField:@"Range"]; // 3. 下载 _downloadTask = [self.session dataTaskWithRequest:request]; } return _downloadTask; } /** 点击按钮 -- 使用NSURLSession断点下载(支持离线) */ - (IBAction)OfflinResumeDownloadBtnClicked:(UIButton *)sender { // 按钮状态取反 sender.selected = !sender.isSelected; if (sender.selected) { // [开始下载/继续下载] // 沙盒文件路径 NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"QQ_V5.4.0.dmg"]; NSInteger currentLength = [self fileLengthForPath:path]; if (currentLength > 0) { // [继续下载] self.currentLength = currentLength; } [self.downloadTask resume]; } else { [self.downloadTask suspend]; self.downloadTask = nil; } } /** * 获取已下载的文件大小 */ - (NSInteger)fileLengthForPath:(NSString *)path { NSInteger fileLength = 0; NSFileManager *fileManager = [[NSFileManager alloc] init]; // default is not thread safe if ([fileManager fileExistsAtPath:path]) { NSError *error = nil; NSDictionary *fileDict = [fileManager attributesOfItemAtPath:path error:&error]; if (!error && fileDict) { fileLength = [fileDict fileSize]; } } return fileLength; }
4 文件上传:文件上传则需要用到POST方法,套路其实都很接近,只不过会有一个formData参数需要传入,这个参数就是一些上传文件的NSData
// 1、请求字符串 NSString *str = [NSString stringWithFormat:@"http://swiftcafe.io/2015/12/20/nsurlsession/thumbnail.php"]; //2、转化成url NSURL *url = [NSURL URLWithString:[str stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]]; //3、构建request NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST"; //4、获取session NSURLSession *session = [NSURLSession sharedSession]; //5、用session发送请求,返回一个uploadTask NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:[NSData dataWithContentsOfFile:@"IMG_0359.jpg"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error == nil) { NSLog(@"upload success:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); } else { NSLog(@"upload error:%@",error); } }]; //6、开始上传 [uploadTask resume]; 最后实现相关的NSURLSessionDataDelegate方法 #pragma mark - <NSURLSessionDataDelegate> 实现方法 /** * 接收到响应的时候:创建一个空的沙盒文件 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { // 获得下载文件的总长度:请求下载的文件长度 + 当前已经下载的文件长度 self.fileLength = response.expectedContentLength + self.currentLength; // 沙盒文件路径 NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"QQ_V5.4.0.dmg"]; NSLog(@"File downloaded to: %@",path); // 创建一个空的文件到沙盒中 NSFileManager *manager = [NSFileManager defaultManager]; if (![manager fileExistsAtPath:path]) { // 如果没有下载文件的话,就创建一个文件。如果有下载文件的话,则不用重新创建(不然会覆盖掉之前的文件) [manager createFileAtPath:path contents:nil attributes:nil]; } // 创建文件句柄 self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:path]; // 允许处理服务器的响应,才会继续接收服务器返回的数据 completionHandler(NSURLSessionResponseAllow); } /** * 接收到具体数据:把数据写入沙盒文件中 */ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { // 指定数据的写入位置 -- 文件内容的最后面 [self.fileHandle seekToEndOfFile]; // 向沙盒写入数据 [self.fileHandle writeData:data]; // 拼接文件总长度 self.currentLength += data.length; NSLog(@"%ld",self.currentLength); __weak typeof(self) weakSelf = self; // 获取主线程,不然无法正确显示进度。 NSOperationQueue* mainQueue = [NSOperationQueue mainQueue]; [mainQueue addOperationWithBlock:^{ // 下载进度 weakSelf.progressView.progress = 1.0 * weakSelf.currentLength / weakSelf.fileLength; weakSelf.progressLabel.text = [NSString stringWithFormat:@"当前下载进度:%.2f%%",100.0 * self.currentLength / self.fileLength]; }]; } /** * 下载完文件之后调用:关闭文件、清空长度 */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { // 关闭fileHandle [self.fileHandle closeFile]; self.fileHandle = nil; // 清空长度 self.currentLength = 0; self.fileLength = 0; }