提供的功能
- 通过URL将数据下载到内存
- 通过URL将数据下载到文件系统
- 将数据上传到指定的URL
- 在后台完成上述操作(支持后台网络操作,除非用户强行关闭应用程序)
NSURLConnection和NSURLSession的关系示意图
NSURLSession中的请求都看做一个请求任务(task)。
NSURLSession的Task继承关系如下:
- NSURLSessionTask 是一个抽象类,提供了一些基本的方法
- NSURLSessionDataTask 是一个具体的 task 类,可以获取数据
- NSURLSessionDownloadTask 是一个具体的 task 类,可以下载数据
- NSURLSessionUploadTask 是一个具体的 task 类,可以上传数据
- NSURLSessionStreamTasj 是一个具体的 task 类,以流的方式请求数据,使用较少
每一个类都有对应的协议。
代码体现
基本使用
- (void)sesssion {
// 1. url
NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/demo.json"];
// 2. 如果使用 GET 请求,request 可以省略
// session - 苹果为了方便程序员的开发,提供了一个全局的 session 单例 (我们是直接获取的,不需要创建)
NSURLSession *session = [NSURLSession sharedSession];
// 3. 数据任务 - 所有的任务,都是由session 发起的
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// 4.将得到数据进行返序列化
NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]); }];
// 5. 启动任务(一定要启动任务)
[task resume];
}
注意以上发起的DataTask任务中:
- (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 中的数据是请求的全部数据.
// @param request 请求对象
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
withRequest方法可以手动设置请求对象,这样就可以指定GET/POST中的任何一种方法了。而withURL方法只能使用GET方法。
在第三方框架对于 NSURLSession 的基本的封装思路就是这样,给一个 url ,后直接启动就可以了。
- (void)sesssionDemo {
// 1. url
NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/demo.json"];
// 2. 启动会话
[self taskWithURL:url];
}
- (void)taskWithURL:(NSURL *)url {
// 1. 全局会话发起数据任务 - 所有的任务,都是由session 发起的
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"%@", [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]); }] resume];
}
但是使用单例类的缺点就是不能实时监控获取数据的情况。因为以上两种方法中只有当请求完全部的数据后才会调用回调方法,如果想要实时获取并监控请求进度,就要设置代理方法。
NSURLSession下载进度跟进
下载进度的跟进是通过代理实现的。
需要跟踪进度,就需要使用代理,代理是一对一的关系,我就要使用一个自定义的会话。
@property(strong, nonatomic)NSURLSession *session;
-
注意delegate Queue如果是传代理方法在子线程中运行时,则UI的刷新要注意在主线程进行。
-
而主队列是
[NSOperationQueue mainQueue]
-
如何选择队列: 网络访问结束后,如果不需要做复杂的操作,可以指定主队列,这样不用考虑线程间通讯。
-
由于下载本身是异步的,不会阻塞主线程工作。
-
在主线程中代理的话,所有的代理回调,都是在主线程异步的,不会阻塞主线程执行!(主线程异步并不会开辟新的线程, 只是把队列任务放到异步执行, 等待主队列中的当前任务都完成, 主线程空闲的时候, 再来执行需要执行的任务)。
// @param configuration 配置信息对象
// @param delegate 代理对象
// @param queue 代理方法在哪个线程中运行,如果传 nil 则会在子线程中运行代理方法
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;
- (void)sessionDownload {
// 1. url
NSURL *url = [NSURL URLWithString:@"http://127.0.0.1/123.mp4"];
// 2. 下载
// 进度跟进:代理,苹果的头文件中,代理都是定义在后面的
// 注意:如果要跟进下载进度,不能使用全局会话
// 这个方法是和下面那个方法是矛盾的,使用了这个方法下面那个必须实现的代理方法是不能使用的
//
[[self.session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
//
// 这个是一个完成的输出
//
NSLog(@"%@ %@", location.path, [NSThread currentThread]);
//
}] resume];
要想使用下面的代理方法的话:直接使用如下方法下载数据。
// 直接启动任务,可以通过代理跟踪进度
[[self.session downloadTaskWithURL:url] resume];}
以下是必须实现的协议方法,表示下载完成了
- location表示文件存储的路径
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
};
以下是断点续传,可以什么都不写,这个是用来实现下载跟进的
在恢复下载的时候可以调用该方法:
- fileOffset表示从什么地方开始下载
- expectedTotalBytes表示文件的总大小。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{
};
以下方法是用来进行下载跟进的 ,下载进度的跟进存在的问题,存在一个峰值的问题。
-
downloadTask 表示下载任务
-
bytesWritten 表示本次写入的数据大小
-
totalBytesWritten 下载的数据总大小
-
totalBytesExpectedToWrite 文件的总大小
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ // 下载的进度
float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;
// 打印下载的进度
NSLog(@"progress = %f",progress);
}
NSURLSession的断点续传问题
断点续传主要考虑的是将会话暂停继续还是将任务进行暂停继续。
NSURLSession 提供的断点续传存在的问题,不能"停",应用程序不能终止,一旦终止,续传数据就丢失了,下次还需要重头再来!
@property (nonatomic, strong) NSURLSession *session;/// 下载任务
@property (nonatomic, strong) NSURLSessionDownloadTask *task;/// 续传数据
- 启动下载任务
- (IBAction)start {
// 1. url
NSURL *url = [NSURL URLWithString:@"http://dldir1.qq.com/qqfile/QQforMac/QQ_V4.0.2.dmg"];
// 发起下载会话
self.task = [self.session downloadTaskWithURL:url];
// 启动任务
[self.task resume];
}
- 暂停下载任务
/// 暂停 - 下载任务
- (IBAction)pause {
NSLog(@"暂停");
// 使用需要续传的数据,暂停下载任务
// 暂停方法,只需要执行一次,被暂停就不用再次暂停
/** 1. 所有的任务都是由会话发起的
2. 当任务启动后,会话会对任务进行强引用
3. 一旦任务被取消,意味着会话不再引用任务,一个 weak 对象没有其他对象强引用,会被立即释放
4. 如果属性 是 strong,除了会话之外,vc同样对任务强引用(session 为什么要使用强引用)
*/
// 这个是取消下载任务 (达到暂停的目的)
[self.task cancelByProducingResumeData:^(NSData *resumeData) {
NSLog(@"---> %tu", resumeData);
// 记录续传数据
self.resumeData = resumeData;
// 将暂停时候的数据保存起来(如果不保存下次启动的时候是没有数据的)
// 释放了下载任务
self.task = nil;
// 将下载任务释放避免多次暂停的问题
}];
}
- 继续下载任务
- (IBAction)resume {
// 需要避免下载任务重复创建
if (self.resumeData == nil) {
NSLog(@"没有续传数据");
return;
}
// 使用续传数据,重新实例化下载任务
// 一旦续传的下载任务被建立之后,续传数据就没有用了!
self.task = [self.session downloadTaskWithResumeData:self.resumeData]; self.resumeData = nil;
[self.task resume];
}