华山论剑之浅谈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

相关文章

python web 开发之Flask中间件与请求处理钩子的最佳实践

《pythonweb开发之Flask中间件与请求处理钩子的最佳实践》Flask作为轻量级Web框架,提供了灵活的请求处理机制,中间件和请求钩子允许开发者在请求处理的不同阶段插入自定义逻辑,实现诸如... 目录Flask中间件与请求处理钩子完全指南1. 引言2. 请求处理生命周期概述3. 请求钩子详解3.1

Linux网络配置之网桥和虚拟网络的配置指南

《Linux网络配置之网桥和虚拟网络的配置指南》这篇文章主要为大家详细介绍了Linux中配置网桥和虚拟网络的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 一、网桥的配置在linux系统中配置一个新的网桥主要涉及以下几个步骤:1.为yum仓库做准备,安装组件epel-re

python如何下载网络文件到本地指定文件夹

《python如何下载网络文件到本地指定文件夹》这篇文章主要为大家详细介绍了python如何实现下载网络文件到本地指定文件夹,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下...  在python中下载文件到本地指定文件夹可以通过以下步骤实现,使用requests库处理HTTP请求,并结合o

Linux高并发场景下的网络参数调优实战指南

《Linux高并发场景下的网络参数调优实战指南》在高并发网络服务场景中,Linux内核的默认网络参数往往无法满足需求,导致性能瓶颈、连接超时甚至服务崩溃,本文基于真实案例分析,从参数解读、问题诊断到优... 目录一、问题背景:当并发连接遇上性能瓶颈1.1 案例环境1.2 初始参数分析二、深度诊断:连接状态与

Spring Boot Controller处理HTTP请求体的方法

《SpringBootController处理HTTP请求体的方法》SpringBoot提供了强大的机制来处理不同Content-Type​的HTTP请求体,这主要依赖于HttpMessageCo... 目录一、核心机制:HttpMessageConverter​二、按Content-Type​处理详解1.

Android与iOS设备MAC地址生成原理及Java实现详解

《Android与iOS设备MAC地址生成原理及Java实现详解》在无线网络通信中,MAC(MediaAccessControl)地址是设备的唯一网络标识符,本文主要介绍了Android与iOS设备M... 目录引言1. MAC地址基础1.1 MAC地址的组成1.2 MAC地址的分类2. android与I

浅谈Redis Key 命名规范文档

《浅谈RedisKey命名规范文档》本文介绍了Redis键名命名规范,包括命名格式、具体规范、数据类型扩展命名、时间敏感型键名、规范总结以及实际应用示例,感兴趣的可以了解一下... 目录1. 命名格式格式模板:示例:2. 具体规范2.1 小写命名2.2 使用冒号分隔层级2.3 标识符命名3. 数据类型扩展命

一文详解如何在Vue3中封装API请求

《一文详解如何在Vue3中封装API请求》在现代前端开发中,API请求是不可避免的一部分,尤其是与后端交互时,下面我们来看看如何在Vue3项目中封装API请求,让你在实现功能时更加高效吧... 目录为什么要封装API请求1. vue 3项目结构2. 安装axIOS3. 创建API封装模块4. 封装API请求

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

SpringBoot请求参数接收控制指南分享

《SpringBoot请求参数接收控制指南分享》:本文主要介绍SpringBoot请求参数接收控制指南,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring Boot 请求参数接收控制指南1. 概述2. 有注解时参数接收方式对比3. 无注解时接收参数默认位置