华山论剑之浅谈iOS的文件下载,断点下载(基于NSURLSession的网络请求)

2023-11-25 21:40

本文主要是介绍华山论剑之浅谈iOS的文件下载,断点下载(基于NSURLSession的网络请求),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

疯狂的程序员决不是靠狂妄和拼命的程序员,而是能够脚踏实地,持续努力的程序员,一个程序员真正做到这两点,技术上去后,唯一能限制他的只有想象力,到那个时候才算“疯狂的程序员”,这种程序员,才能令对手无比恐惧。

美丽的风景图片


前言

上面的一张水域小镇风景图是那么的美丽,美丽的东西总是令人向往.现在我想它从网上下载下来当我的手机桌面的背景图,那么该怎么办?如果图片的很小,我们该如何做,如果图片过大我们又该如何处理呢?或者说是当我们需要下载一个几百兆的文件的时候,我们改如何处理呢?


####文件的一次性下载


做应用程序的时候,不管我们是使用第三方网络请求类AFNetworking、ASIHTTPRequest,还是原生态的NSURLSession和NSURLConnection,我们请求后台数据大多数是一次请求完成的,现在我使用NSData自带的方法下载一下上面的图片.为了方便,我直接使用storyboard做的

控制器上的各个控件

"全部下载"按钮的代码如下.

//一次性下载所有数据
- (IBAction)loadAllData:(id)sender {//使用NSData 直接下载文件NSURL *urlString = [NSURL URLWithString:@"http://www.deskcar.com/desktop/fengjing/20125700336/18.jpg"];NSData *data = [NSData dataWithContentsOfURL:urlString];NSLog(@"%@",data);self.imageView.image  = [UIImage imageWithData:data];self.imageView.contentMode = UIViewContentModeScaleAspectFit;}

当然,这里我直接使用的主线程请求网络数据,其实应该开辟一个子线程做请求网络数据,但是我们在主线程中可以轻易的看到 "全部下载"按钮的卡顿(如下图),造成的原因一个是图片文件太大,另外一个就是没有开辟子线程,文件太大的时候,我们就可以使用断点下载了.

按钮的卡顿现象严重


####大文件的直接下载和断点下载


大文件的下载在这里我说一下 iOS原生态网络请求类NSURLSession 的直接下载和断点下载,NSURLConnection由于这个类已经被弃用了,所以我就不多言语了.

直接下载 我们不能再用以前的简单粗暴的方法直接把从网络中请求到的数据直接放到内存中,那样的话,会严重影响到程序中的其他功能.我们应该直接把请求到的数据直接放到沙盒当中,进行数据的持久化.对于直接下载我们用到的是NSURLSessionTask的子类NSURLSessionDownloadTask,不管是直接下载还是断点续传,我们都需要遵守NSURLSessionDownloadDelegate协议,并且对协议中的方法进行实现.

注意 : 使用代理方法实现网络请求的时候,不能同时再使用网络请求对象中的block块,因为block的优先级高于代理方法,所以同时使用代理方法是不执行的!!!

我们看一下文件的直接下载的时候,我们都需要用到那几个代理方法.

写入数据
/***  **  @param session*  @param downloadTask              当前下载任务*  @param bytesWritten              当前这次写入数据的大小*  @param totalBytesWritten         已经写入数据的大小*  @param totalBytesExpectedToWrite 预计写入数据的总大小*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{}
下载完成时
/***  **  @param session*  @param downloadTask 当前下载任务 (属性中有响应头)*  @param location     下载的位置*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {}
完成文件下载任务
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{      }
还是拿上面的那张壁纸的URL为例(壁纸的大小大约有1.2M),我们对其直接做网络下载.不说话,直接上代码.

#pragma mark --- 文件直接下载 ----- (IBAction)breakpointData:(id)sender {//设置代理NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];NSURL *urlString = [NSURL URLWithString:@"http://www.deskcar.com/desktop/fengjing/20125700336/18.jpg"];NSURLSessionDownloadTask *downLoadTask = [session downloadTaskWithURL:urlString];//启动下载任务[downLoadTask resume];self.progressView = [MBProgressHUD showHUDAddedTo:self.view animated:YES];// Set the bar determinate mode to show task progress.self.progressView.mode = MBProgressHUDModeDeterminateHorizontalBar;self.progressView.label.text = @"文件下载中....";}#pragma mark - NSURLSessionDownloadDelegate- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{//MBProgressHUD进度条显示self.progressView.progress = (float)1.0*totalBytesWritten / totalBytesExpectedToWrite ;}- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{//根据请求头中的文件名在沙盒中直接创建路径NSURLResponse *response = downloadTask.response;NSString *filePaths =[self cacheDir:response.suggestedFilename];self.filePaths = filePaths;NSFileManager *fileManager = [NSFileManager defaultManager];//将临时的下载文件(在内存中)放入沙盒中.[fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePaths] error:nil];}// 完成任务
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{if (self.progressView.progress == 1.0) {self.imageView.image = [UIImage imageWithContentsOfFile:self.filePaths];[self.progressView hideAnimated: YES];}
}#pragma mark --- 输入一个字符串,则在沙盒中生成路径
// 传入字符串,直接在沙盒Cache中生成路径
- (NSString *)cacheDir:(NSString *)paths
{NSString *cache = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;return [cache stringByAppendingPathComponent:[paths lastPathComponent]];
}


断点下载 断点下载的核心以及和直接下载的区别就是请求头Rang.我们先看些关于Rang相关的知识.

只要设置HTTP请求头的Range属性, 就可以实现从指定位置开始下载
表示头100个字节:Range: bytes=0-99
表示第二个100字节:Range: bytes=100-199
表示最后100个字节:Range: bytes=-100
表示100字节以后的范围:Range: bytes=100-

如下设置请求头

//设置请求头 ,这个是从什么位置开始到最后,不懂看上面的Range属性的设置NSString *range = [NSString stringWithFormat:@"bytes=%ld-",self.currentLength]; [request setValue:range forHTTPHeaderField:@"Range"];

当使用NSURLSessionDownloadTask的时候,我们就可以不用设置请求头,因为系统给封装了两个方法,使我们可以更简单的进行断点续传.

一个是任务暂停时候的的带有block回调函数的方法,方法中有个NSData类型的参数resumeData是用于记录下载的URL地址和已下载的总共的字节数两部分,而不是直接存储的已下载的数据.我们需要做的就是把resumeData保存下来,用于后面的断点续传.

- (void)cancelByProducingResumeData:(void (^)(NSData * __nullable resumeData))completionHandler;

另外一个就是NSURLSession 自带的使用resumeData创建NSURLSessionDownloadTask的初始化方法.我们只要把上面的resumeData的传过来创建就可以了.

- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;

当然,我们还是要对下载进队做监控,那么还是要实现NSURLSessionDownloadDelegate的协议中的方法.那么不多说,直接上代码和原型图.


#import "ViewController.h"@interface ViewController ()<NSURLSessionDataDelegate>@property (strong, nonatomic) IBOutlet UIImageView *imageView;//图片@property (strong, nonatomic) IBOutlet UIButton *breakpointButton;@property (strong, nonatomic) IBOutlet UILabel *progressLabel;@property(nonatomic,strong)NSString *filePaths;//文件的沙盒路径@property(nonatomic,assign)NSInteger fileSize;//本地已经下载的文件的大小@property(nonatomic,assign)NSInteger altogetherSize;//文件总共的大小@property (nonatomic, strong) NSURLSessionDownloadTask *task;@property (nonatomic, strong) NSData *resumeData;@property (nonatomic, strong) NSURLSession *session;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];self.imageView.contentMode = UIViewContentModeScaleAspectFit;//图片大小自适应self.filePaths = 0;self.fileSize = 0;self.altogetherSize = 0;}#pragma mark --- 断点下载 --- - (IBAction)breakpointData:(UIButton *)sender {if (self.task == nil) { // 开始(继续)下载if (self.resumeData) { // 恢复[sender setTitle:@"暂停" forState:UIControlStateNormal];[self resume];} else { // 开始[self start];[sender setTitle:@"暂停" forState:UIControlStateNormal];}} else { // 暂停[sender setTitle:@"继续" forState:UIControlStateNormal];[self pause];}}//懒加载
- (NSURLSession *)session
{if (!_session) {// 获得sessionNSURLSessionConfiguration *cfg = [NSURLSessionConfiguration defaultSessionConfiguration];self.session = [NSURLSession sessionWithConfiguration:cfg delegate:self delegateQueue:[NSOperationQueue mainQueue]];}return _session;
}- (void)start
{// 1.创建一个下载任务NSURL *url = [NSURL URLWithString:@"http://www.deskcar.com/desktop/fengjing/20125700336/18.jpg"];self.task = [self.session downloadTaskWithURL:url];// 2.开始任务[self.task resume];
}- (void)resume
{// 传入上次暂停下载返回的数据,就可以恢复下载self.task = [self.session downloadTaskWithResumeData:self.resumeData];// 开始任务[self.task resume];// 清空self.resumeData = nil;
}- (void)pause
{__weak typeof(self) vc = self;[self.task cancelByProducingResumeData:^(NSData *resumeData) {//  resumeData : 包含了继续下载的开始位置\下载的urlvc.resumeData = resumeData;vc.task = nil;}];
}#pragma mark - NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{//根据请求头中的文件名在沙盒中直接创建路径NSURLResponse *response = downloadTask.response;NSString *filePaths =[self cacheDir:response.suggestedFilename];self.filePaths = filePaths;NSFileManager *fileManager = [NSFileManager defaultManager];//将临时的下载文件(在内存中)放入沙盒中.[fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePaths] error:nil];self.imageView.image = [UIImage imageWithContentsOfFile:self.filePaths];}- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTaskdidWriteData:(int64_t)bytesWrittentotalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{if (totalBytesExpectedToWrite > self.altogetherSize) {self.altogetherSize = totalBytesExpectedToWrite;NSLog(@"%ld",(long)self.altogetherSize);}NSLog(@"%f",(double)totalBytesWritten / self.altogetherSize);self.progressLabel.text = [NSString stringWithFormat:@"%.0f %",(double)100*totalBytesWritten / self.altogetherSize];if ((double)totalBytesWritten / self.altogetherSize == 1) {//关掉用户交互[self.breakpointButton setTitle:@"完成" forState:UIControlStateNormal];self.breakpointButton.userInteractionEnabled = NO;}
}#pragma mark --- 输入一个字符串,则在沙盒中生成路径
// 传入字符串,直接在沙盒Cache中生成路径
- (NSString *)cacheDir:(NSString *)paths
{NSString *cache = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;return [cache stringByAppendingPathComponent:[paths lastPathComponent]];
}@end

开始界面

下载过程中

完成页面



总结: 断点续传以及大文件的下载在我们的程序开发过程中时常用到,用途比较广泛,比如开发一个应用商店,一个书架App等等,而且NSURLSession的断点续传比较简单.希望这篇文章对您的开发能有所帮助.最后附上自己做的Demo,不懂在评论区回复,我会及时回复您,谢谢.
------ > 🚀Demo的传送门

这篇关于华山论剑之浅谈iOS的文件下载,断点下载(基于NSURLSession的网络请求)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/424651

相关文章

前端下载文件时如何后端返回的文件流一些常见方法

《前端下载文件时如何后端返回的文件流一些常见方法》:本文主要介绍前端下载文件时如何后端返回的文件流一些常见方法,包括使用Blob和URL.createObjectURL创建下载链接,以及处理带有C... 目录1. 使用 Blob 和 URL.createObjectURL 创建下载链接例子:使用 Blob

鸿蒙中Axios数据请求的封装和配置方法

《鸿蒙中Axios数据请求的封装和配置方法》:本文主要介绍鸿蒙中Axios数据请求的封装和配置方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1.配置权限 应用级权限和系统级权限2.配置网络请求的代码3.下载在Entry中 下载AxIOS4.封装Htt

springboot filter实现请求响应全链路拦截

《springbootfilter实现请求响应全链路拦截》这篇文章主要为大家详细介绍了SpringBoot如何结合Filter同时拦截请求和响应,从而实现​​日志采集自动化,感兴趣的小伙伴可以跟随小... 目录一、为什么你需要这个过滤器?​​​二、核心实现:一个Filter搞定双向数据流​​​​三、完整代码

浅谈配置MMCV环境,解决报错,版本不匹配问题

《浅谈配置MMCV环境,解决报错,版本不匹配问题》:本文主要介绍浅谈配置MMCV环境,解决报错,版本不匹配问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录配置MMCV环境,解决报错,版本不匹配错误示例正确示例总结配置MMCV环境,解决报错,版本不匹配在col

AJAX请求上传下载进度监控实现方式

《AJAX请求上传下载进度监控实现方式》在日常Web开发中,AJAX(AsynchronousJavaScriptandXML)被广泛用于异步请求数据,而无需刷新整个页面,:本文主要介绍AJAX请... 目录1. 前言2. 基于XMLHttpRequest的进度监控2.1 基础版文件上传监控2.2 增强版多

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

浅谈mysql的sql_mode可能会限制你的查询

《浅谈mysql的sql_mode可能会限制你的查询》本文主要介绍了浅谈mysql的sql_mode可能会限制你的查询,这个问题主要说明的是,我们写的sql查询语句违背了聚合函数groupby的规则... 目录场景:问题描述原因分析:解决方案:第一种:修改后,只有当前生效,若是mysql服务重启,就会失效;

Python下载Pandas包的步骤

《Python下载Pandas包的步骤》:本文主要介绍Python下载Pandas包的步骤,在python中安装pandas库,我采取的方法是用PIP的方法在Python目标位置进行安装,本文给大... 目录安装步骤1、首先找到我们安装python的目录2、使用命令行到Python安装目录下3、我们回到Py