如人饮水,冷暖自知

iOS 模块详解—「数据请求:NSURLSession 会话」

引导


苹果在 iOS9 之后已经废弃了 NSURLConnection,所以在现在的实际开发中,除了大家常见的 AFN 框架,一般使用的是 iOS7 之后推出的 NSURLSession,作为一名iOS 开发者,如果你只知道 AFN 框架来进行网络请求,那 ~ 。

本篇文章主要从【NSURLConnection】学习总结,该模块学习将续更 ~
在「时间 & 知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出,可留言指正或是补充,以提高文章质量@白开水ln原著;

目录:

  1. NSURLSession 介绍
  2. NSURLSession 优势
  3. NSURLSession 子类基本使用
  4. NSURLSessionDownloadTask 大文件下载
  5. NSURLSessionDataTask 断点下载 | 支持离线
  6. URLSessionDataTask 断点下载效果
  7. NSURLSession.h
  8. SourceCodeToolsClassWechatPublic-Codeidea

1. NSURLSession 介绍

1
2
3
4
5
6
7
8
9
10
11
关于task
NSURLSessionTask 是一个抽象类,是一个抽象类,本身不能使用,只能使用它的子类
NSURLSessionTask 有两个子类
1.NSURLSessionDataTask:可以用来处理一般的网络请求,如 GET | POST 请求
DataTask子类 NSURLSessionUploadTask:用于处理上传请求的时候有优势
2.NSURLSessionDownloadTask:主要用于处理下载请求,有很大的优势
使用步骤
1.使用NSURLSession创建task
2.执行task [dataTask resume]

Task的类型
Task 类型.png

2. NSURLSession 优势

1
2
3
4
5
6
1.NSURLSession 支持 http2.0 协议
2.在处理下载任务的时候可以直接把数据下载到磁盘
3.支持后台下载|上传
4.同一个 session 发送多个请求,只需要建立一次连接(复用了TCP)
5.提供了全局的 session 并且可以统一配置,使用更加方便
6.下载的时候是多线程异步处理,效率更高

3. NSURLSession 子类基本使用

1.GET请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 1.确定url
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];
// 2.创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 3.创建Session会话对象(可以获取单例对象)
NSURLSession *session = [NSURLSession sharedSession];
// 4.创建Task
/**
 Request:请求对象
 completionHandler 当请求完成之后调用
 data:响应体信息
 response:响应头信息
 error:错误信息当请求失败的时候,error有值
 */
// 方法一:dataTaskWithRequest: completionHandler:
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // 拿到响应头信息
    NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
    // 6.解析数据
    NSLog(@"%@\n%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding],res.allHeaderFields);
}];
-----------------------------------------
// 方法二:dataTaskWithURL: completionHandler:
// 注意:dataTaskWithURL 内部会自动的将请求路径作为参数创建一个请求对象
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
// 5.执行Task
[dataTask resume];
//[dataTask cancel];// 取消任务
//[dataTask suspend];// 暂停任务
2.POST请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1.确定url
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];
// 2.创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 3.创建Session会话对象(可以获取单例对象)
NSURLSession *session = [NSURLSession sharedSession];
// 设置 请求方法
request.HTTPMethod = @"POST";
// 设置 请求体
request.HTTPBody = [@"username=520&pwd=520&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];
// 设置 请求超时
//request.timeoutInterval = 10;
// 设置 请求头User-Agent
// 注意:key一定要一致(用于传递数据给后台)
//[request setValue:@"ios 10.1" forHTTPHeaderField:@"User-Agent"];
// 4.创建Task
// 注意:如果要发送POST请求,那么请使用dataTaskWithRequest,设置一些请求属性
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // 6.解析数据
}];
// 5.执行Task
[dataTask resume];
3.设置代理发送请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1.确定URL
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];
// 2.创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 3.创建会话对象,设置代理
/**
 Configuration:配置信息,用默认的即可
 delegate:代理
 delegateQueue:设置代理方法在哪个线程中执行
 */
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
// 4.创建Task
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
// 5.执行Task
[dataTask resume];

遵守<NSURLSessionDataDelegate> 实现 代理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 1.接收到服务器的时候
/**
 session 会话对象
 dataTask 请求任务
 response 响应头信息
 completionHandler 回调,传给系统
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
        /**
         NSURLSessionResponseCancel = 0, 取消请求,默认
         NSURLSessionResponseAllow = 1, 接收数据                                  
         NSURLSessionResponseBecomeDownload = 2, 变成下载任务                              
         NSURLSessionResponseBecomeStream 变成下载任务 9.0之后可以用
         */
        completionHandler(NSURLSessionResponseAllow);
}
// 2.接收到服务器返回的数据,会调用多次(data可能是部分)
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    // 拼接数据
    [self.fileData appendData:data];
}
// 3.请求完成 或 失败的时候调用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    if (error) { return; } else {
        // 解析数据
        NSLog(@"%@",[[NSString alloc] initWithData:self.fileData encoding:NSUTF8StringEncoding]);
    }
}

4. 设置代理之后的强引用问题
1
2
3
4
5
6
7
问题:`NSURLSession` 对象在使用的时候,如果设置了代理,
     那么 session 会对代理对象保持一个强引用,在不用的时候应该主动进行释放
解决:在`dealloc`方法中进行释放,
     可以通过调用 `finishTasksAndInvalidate`或`resetWithCompletionHandler`  来释放对代理对象的强引用
[self.session finishTasksAndInvalidate];

4. NSURLSessionDownloadTask 大文件下载

1.DownloadTask:Block方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 1.确定URL
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_03.png"];
// 2.创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 3.创建会话对象
NSURLSession *session = [NSURLSession sharedSession];
// 4.创建Task
/**
 downloadTaskWithRequest: Block方式
 注意:该方法内部已经实现了边接受数据边写入沙盒(tmp临时文件目录)的操作,
 需要剪切文件,把它移动到我们指定的位置。
 
*/
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // 6.数据解析
    NSLog(@"%@---%@",location,[NSThread currentThread]);
    // 7.拼接文件全路径
    // 拼接文件后的本地名称:FileName @"123.png"或 [url lastPathComponent] 获取URL最后一个字节命名
    NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectoryNSUserDomainMaskYES) lastObject] stringByAppendingPathComponent:[url lastPathComponent]];
    // 8.剪切文件
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil];
    NSLog(@"%@",fullPath);
}];
// 5.执行Task
[downloadTask resume];


DownloadTask:Block方式
优点:不需要担心内存(边接受数据边写入沙盒(tmp临时文件目录)的操作)
缺点:无法监听文件下载进度

2.DownloadTask:Dlegate方式

遵守<NSURLSessionDownloadDelegate>协议,实现代理方法。可以在didWriteData(写数据)代理方法,监听下载进度

1
2
3
4
5
6
7
8
NSURL *url = [NSURL URLWithString:@"[http://120.25.226.186:32812/resources/images/minion_03.png"];](http://120.25.226.186:32812/resources/images/minion_03.png%22];)
// Configuration:配置信息,用默认的即可
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:[NSURLRequest requestWithURL:url]];
[downloadTask resume];// 执行Task


DownloadTask:Dlegate方式,解决了无法监听下载进度的问题

5.DownloadTask断点下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 开始下载
- (IBAction)starBtnClick:(id)sender {
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
    self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    
    self.downloadTask = [self.session downloadTaskWithRequest:[NSURLRequest requestWithURL:url]];
    [self.downloadTask resume];
}
// 暂停下载(是可以恢复的)
- (IBAction)suspendBtnClick:(id)sender {
    NSLog(@"+++++++++++++++++++暂停");
    [self.downloadTask suspend];
}
// 取消下载(注意)
// cancel:取消是不能恢复
// cancelByProducingResumeData:是可以恢复
- (IBAction)cancelBtnClick:(id)sender {
    NSLog(@"+++++++++++++++++++取消");
    //[self.downloadTask cancel];
    [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        self.resumData = resumeData;
    }];
}
// 继续下载
- (IBAction)goOnBtnClick:(id)sender {
    NSLog(@"+++++++++++++++++++继续下载");
    if (self.resumData) {
        // 恢复下载数据(文件保存信息,保存到那个字节) != 文件数据
        self.downloadTask = [self.session downloadTaskWithResumeData:self.resumData];
    }
    [self.downloadTask resume];
}

NSURLSessionDownloadDelegate代理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
 1.写数据(监听下载进度)
 session 会话对象
downloadTask 下载任务
 bytesWritten 本次写入的数据大小
 totalBytesWritten 下载的数据总大小
 totalBytesExpectedToWrite 文件的总大小
 */
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
    // 获得文件的下载进度
    NSLog(@"%f",1.0 * totalBytesWritten/totalBytesExpectedToWrite);
    self.proessView.progress = 1.0 * totalBytesWritten/totalBytesExpectedToWrite;
}
/**
 2.当恢复下载的时候调用方法
 fileOffset 从什么地方下载
 expectedTotalBytes 文件的总大小
 */
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
    NSLog(@"恢复下载--%s",__func__);
}
/**
3.当下载完成的时候调用
location 文件的临时存储路径
*/
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    NSLog(@"文件的临时存储路径--%@",location);
    
    // 1.拼接文件全路径
    // downloadTask.response.suggestedFilename 文件名称
    NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectoryNSUserDomainMaskYES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
    
    // 2.剪切文件
    [[NSFileManager defaultManager]moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil];
    NSLog(@"%@",fullPath);
}
/**
 4.请求失败
 */
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"didCompleteWithError");
}


1
2
3
局限性:
1.如果用户点击暂停之后退出程序,那么需要把恢复下载的数据写一份到沙盒,代码复杂度高
2.如果用户在下载中途未保存恢复下载数据即退出程序,则不具备可操作性

6. NSURLSessionDataTask 断点下载 | 支持离线

NSURLSessionDataTask 实现大文件
1.开始下载、暂停下载、取消下载、恢复下载
2.支持后台下载|上传(离线 断点)
3.在处理下载任务的时候可以直接把数据下载到磁盘
4.下载的时候是子线程异步处理,效率更高

属性定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface DataTaskViewController ()<NSURLSessionDataDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *proessView;
/** 创建文件句柄 */
@property (nonatomic, strong) NSFileHandle *handle;
/** 文件的总大小 */
@property (nonatomic, assign) NSInteger totalSize;
/** 当前下载数据大小 */
@property (nonatomic, assign) NSInteger currentSize;
/** 获得文件全路径 */
@property (nonatomic, strong) NSString *fullPath;
/** 创建Task */
@property (nonatomic, strong) NSURLSessionDataTask *dataTask;
/** 创建会话对象 */
@property (nonatomic, strong) NSURLSession *session;

懒加载(方法的独立与抽取):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
-(NSString *)fullPath {
if (!_fullPath) {
// 获得文件全路径
// 拼接文件后的本地名称 FileName @"123.mp4" 或者 [url lastPathComponent] 获取URL最后一个字节命名
_fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:FileName];
}
return _fullPath;
}
-(NSURLSession *)session {
if (!_session) {
// 创建会话对象,设置代理
/*
Configuration:配置信息 [NSURLSessionConfiguration defaultSessionConfiguration]
delegate:代理
delegateQueue:设置代理方法在哪个线程中调用
*/
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
}
return _session;
}
-(NSURLSessionDataTask *)dataTask {
if (!_dataTask) {
// 1.确定url
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
// 2.创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 3 设置请求头信息,告诉服务器请求那一部分数据
self.currentSize = [self getFileSize];
NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentSize];
[request setValue:range forHTTPHeaderField:@"Range"];
// 4.创建Task
_dataTask = [self.session dataTaskWithRequest:request];
}
return _dataTask;
}
-(NSInteger)getFileSize {
// 获得指定文件路径对应文件的数据大小
NSDictionary *fileInfoDict = [[NSFileManager defaultManager]attributesOfItemAtPath:self.fullPath error:nil];
NSLog(@"%@",fileInfoDict);
NSInteger currentSize = [fileInfoDict[@"NSFileSize"] integerValue];
return currentSize;
}

NSURLSessionDataDelegate 代理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#pragma mark - NSURLSessionDataDelegate
// 1.接收到服务器的响应 它默认会取消该请求
/**
session 会话对象
dataTask 请求任务
response 响应头信息
completionHandler 回调 传给系统
expectedContentLength 本次请求的数据大小
*/
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
// 获得文件的总大小
self.totalSize = response.expectedContentLength + self.currentSize;
if (self.currentSize == 0) {
// 创建空的文件
[[NSFileManager defaultManager]createFileAtPath:self.fullPath contents:nil attributes:nil];
}
// 创建文件句柄
self.handle = [NSFileHandle fileHandleForWritingAtPath:self.fullPath];
// 移动指针(每接收到服务器的响应,就移动指针指向文件末尾)
[self.handle seekToEndOfFile];
/*
NSURLSessionResponseCancel = 0,取消 默认
NSURLSessionResponseAllow = 1,接收
NSURLSessionResponseBecomeDownload = 2,变成下载任务
NSURLSessionResponseBecomeStream 变成流
*/
completionHandler(NSURLSessionResponseAllow);
}
// 2.接收到服务器返回的数据 调用多次
/**
session 会话对象
dataTask 请求任务
data 本次下载的数据
*/
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// 写入数据到文件
[self.handle writeData:data];
// 计算文件的下载进度
self.currentSize += data.length;
NSLog(@"%f",1.0 * self.currentSize / self.totalSize);
self.proessView.progress = 1.0 * self.currentSize / self.totalSize;
}
// 3.请求完成 或者 失败的时候调用
/**
session 会话对象
dataTask 请求任务
error 错误信息
*/
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// 关闭文件句柄(创建句柄,要记得在完成方法里 关闭句柄置空)
[self.handle closeFile];
self.handle = nil;
NSLog(@"%@",self.fullPath);
}
// 4.清理工作
/**
NSURLSession,如果设置代理的话会有一个强引用不会被释放掉,当不用Session的时候,
一定要调用finishTasksAndInvalidate 和 invalidateAndCancel 释放掉.
*/
-(void)dealloc {
//finishTasksAndInvalidate
[self.session invalidateAndCancel];
}

Reading


  • 如果在阅读过程中遇到 error || new ideas,希望你能 issue 我,我会及时补充谢谢。

  • 不管谁的博客上面写的 (也包括自己),阅读的你要敢于去验证;如人饮水,冷暖自知;(共勉)。

  • 喜欢可 赞赏 or Star一波;点击左上角关注 或 微众『Codeidea』,在 Demo 更新时收到邮件通知,便捷你的阅读。

🖋 Plain boiled water ln : 本文结束    感谢阅读 ^_^. Need coffee?
👁 At this time suggest 1 minute eye exercises

本文标题:iOS 模块详解—「数据请求:NSURLSession 会话」

文章作者:白开水ln

原始链接:http://plainboiledwaterln.cn/iOSNET/NSURLSession.html

版权声明: 署名-非商业性使用-禁止演绎 4.0 国际 『微众圈:Codeidea』本博客文章除特别声明外均为原创,如需转载请务必保留原链接(可点击进入的链接)和作者出处,谢谢合作!

「喜欢就留言or赞赏」but「支持不要超过你早餐费的 0.5」 ^_^.