一 NSURLSession文件下载
方法一:采用block块回调的方式进行文件下载
涉及到的知识点:
1> 如何将下载的文件存入沙盒中?(直接看代码)
2> 如何拼接文件保存路径?(直接看代码)
block回调优缺点和应用范围:
3 代码中包括文件下载好存入的沙盒路径和文件的拼接
#pragma mark - 下载视频(block回调)
- (void)downloadWithVideo
{
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%s------",__func__);
NSString *fileName = response.suggestedFilename;
NSString *fullName = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:fileName];
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullName] error:nil];
NSLog(@"------------%@",fullName);
}];
[downloadTask resume];
}
4 获取文件路径和剪切下载好的文件到指定的目录中的原因:由于下载好的文件默认是存在temp中的,但是如果没指定好将下载好的文件存入你所指定的位置,那么是找不到下载好的文件.原因是,temp文件是用来保存零时数据的,所以我们需要将下载好的文件存入caches中,因为文件都是存在caches文件中的.
方法二:采用代理的方法进行文件下载
涉及到知识点:
1> 通过手动控制文件的状态(开始下载;继续下载;暂停下载;取消下载)
2> 在storyboard中显示下载的进度
3> 经常需要调用的四个代理方法
4> 如何计算进度
运用代理方法进行下载文件的优缺点:
优点:1> 能够直接把文件下载到沙盒中,我们需要做文件剪切处理(不会有内存飙升的问题)
2> 可以监测文件的下载进度
缺点:NSURLSessionDownloadTask不能实现断点下载(完全退出程序后不能从上一次下载的地方继续下载)
应用:大小文件都可以下载
相关代码及解释
1 用户需要控制的文件下载状态
#pragma mark - 开始下载
- (IBAction)startBtnClick
{
[self downloadWithDataDelegate];
}
#pragma mark - 继续下载
- (IBAction)goOnBtnClick
{
if (self.resumeData) {
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
}
[self.downloadTask resume];
}
#pragma mark - 暂停下载
- (IBAction)suspendBtnClick
{
[self.downloadTask suspend];
}
#pragma mark - 取消
- (IBAction)cancelBtnClick
{
[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
self.resumeData = resumeData;
}];
}
2 主方法(需用self调用该方法,才能执行下载操作)
#pragma mark - 下载视频的代理方法
- (void)downloadWithDataDelegate
{
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithRequest:request];
[downloadTask resume];
self.downloadTask = downloadTask;
}
3 懒加载会话(由于我们整个类很多地方都需要使用到会话,那么我们创建一个懒加载,不需要考虑什么时候创建,需要用到的时候就创建)
#pragma mark - 懒加载会话
- (NSURLSession *)session
{
if (_session == nil) {
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
}
return _session;
}
4 代理方法一:保存下载好的文件
#pragma mark - 下载完成(在这个方法里面完成对数据的存储)
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:nil];
}
5 下载数据,并且给进度条赋值
#pragma mark - 下载数据
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
NSLog(@"%s",__func__);
//计算精度
CGFloat progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
//给进度条赋值
self.progressView.progress = progress;
}
6 如何计算下载的精度?
—解答: [ 1.0 *(用下载好的数 / 总的文件大小数)]–>乘以1.0是将整型转为浮点型
二 文件的断点续传(文件句柄)
1 概念:断点续传就是当用户在下载文件的时候,但是用户突然不想下载了,然后又没有点击取消,而是直接将app退出.但是等到用户再次运行app的时候又突然想下载该文件了,而这里就需要用到断点续传的方法,就是让用户接着从上一次下载的地方接着下载,不需要开始在下载.
—方法: 我所提到的上面的方法都不能满足断点续传的目的,这里我将采用NSURLSessionDataTask这个类的方法实现实现目的.
注意: 使用NSURLSessionDataTask这个类进行文件下载中会涉及到的难点是:
1> 内存飙升的原因是文件拼接
—–解决办法: 使用文件句柄作为拼接数据的方法
2> 进度不准确
3> 文件不完整
4>文件句柄二次创建产生的文件大小太大的问题
5> 解决当用户再次下载的时候,进度条显示到上一次下载的地点
—–解决办法:做判断
2 文件句柄的基本概念
文件句柄:相当于一个指针,指向文件的末尾,功能是能达到文件的追加,不会使得断点下载的文件重叠.
3 代码部分
3.1 相关宏
/沙盒minion_02.mp4的文件路径
#define fullPath [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"minion_02.mp4"]
#define totalPath [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"XFJ.XFJ"]
3.2 属性和协议
@interface ViewController ()<NSURLSessionDataDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@property (nonatomic, strong) NSURLSessionDataTask *dataTask;
@property (nonatomic, assign) NSInteger totalSize;
@property (nonatomic, strong) NSFileHandle *handle;
@property (nonatomic, assign) NSInteger currentSize;
@property (nonatomic, strong) NSURLSession *session;
3.3 用户的具体操作
#pragma mark - 开始下载
- (IBAction)startBtnClick
{
[self.dataTask resume];
}
#pragma mark - 暂停下载
- (IBAction)suspendBtnClick
{
[self.dataTask suspend];
}
#pragma mark - 取消下载
- (IBAction)cancelBtnClick
{
[self.dataTask cancel];
self.dataTask = nil;
}
#pragma mark - 继续下载
- (IBAction)goOnBtnClick
{
[self.dataTask resume];
}
3.4 对会话对象的懒加载和代理的设置
- (NSURLSession *)session
{
if (_session == nil) {
//如果没有会话对象的话就创建一个(里面的队列参数可以填nil,填nil默认在子线程中进行下载)
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
}
return _session;
}
3.5 用户二次运行app的时候对进度条的设置
#pragma mark - view加载完毕的时候调用
- (void)viewDidLoad
{
[super viewDidLoad];
NSDictionary *dictInfo = [[NSFileManager defaultManager] attributesOfItemAtPath:fullPath error:nil];
NSUInteger size = [dictInfo[@"NSFileSize"] integerValue];
self.currentSize = size;
NSData *data = [NSData dataWithContentsOfFile:totalPath];
if (data) {
NSInteger totalSize = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]integerValue];
if (self.currentSize != 0 && totalSize != 0) {
CGFloat progress = 1.0 * self.currentSize / totalSize;
self.progressView.progress = progress;
}
}
}
3.6 懒加载对任务的创建,由于是需要用到文件句柄,也就需要将文件的数据分割为各小段.
#pragma mark - 任务的懒加载
- (NSURLSessionDataTask *)dataTask
{
if (_dataTask == nil) {
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_02.mp4"];
NSMutableURLRequest *requeset = [NSMutableURLRequest requestWithURL:url];
NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentSize];
[requeset setValue:range forHTTPHeaderField:@"Range"];
_dataTask = [self.session dataTaskWithRequest:requeset];
}
return _dataTask;
}
3.7 数据源方法
#pragma mark - 数据源代理
#pragma mark - 接收响应
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
self.totalSize = response.expectedContentLength + self.currentSize;
NSData *dataM = [[NSString stringWithFormat:@"%zd",self.totalSize] dataUsingEncoding:NSUTF8StringEncoding];
[dataM writeToFile:totalPath atomically:YES];
if (self.currentSize == 0) {
[[NSFileManager defaultManager] createFileAtPath:fullPath contents:nil attributes:nil];
}
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:fullPath];
self.handle = handle;
completionHandler(NSURLSessionResponseAllow);
}
3.8 移动文件句柄,计算进度条显示
#pragma mark - 接收数据
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
[self.handle seekToEndOfFile ];
[self.handle writeData:data];
self.currentSize += data.length;
CGFloat progress = 1.0 * self.currentSize / self.totalSize;
self.progressView.progress = progress;
}
3.9 关闭文件句柄,释放文件句柄
#pragma mark - 请求结束时候调用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
[self.handle closeFile];
self.handle = nil;
}
三 文件下载(输出流)
1 输出流:说明白点就是类似于管道通水一样下载,只不过输出流比起文件句柄多了自身会创建一个空的文件夹,也就是如果发现所指向的路径不存在,那么会自动创建一个空的文件夹,用来装数据.
2 代码
2.1 属性
@property (nonatomic, strong) NSOutputStream *stream;
2.2 将接受响应中的文件句柄的相关代码换成下面的代码
NSOutputStream *stream = [[NSOutputStream alloc] initToFileAtPath:fullPath append:YES];
[stream open];
self.stream = stream;
2.3 请求结束的时候关闭输出流,并且释放输出流,否则会造成内存泄露
[self.stream close];
self.stream = nil;
四 NSURLSession的强引用
说明:在整个程序中,我们都对会话进行了强引用,这样会造成内存泄露
解决办法:
#pragma mark - 释放会话(在view显示完毕后)
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[self.session invalidateAndCancel];
}
- (void)dealloc
{
NSLog(@"%s",__func__);
}
五 总结
1 认识文件句柄和输出流,知道怎么用.数据是如何保存的?怎么通过再次的赋值达到断点续传?这些都是需要知道的,我里面的代码都说明白了,一些逻辑问题,如果不懂的话就看代码,里面都有详细解释,要是真有什么难点的话,就给我留言.谢谢!!!!