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

相关文章

Android使用java实现网络连通性检查详解

《Android使用java实现网络连通性检查详解》这篇文章主要为大家详细介绍了Android使用java实现网络连通性检查的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录NetCheck.Java(可直接拷贝)使用示例(Activity/Fragment 内)权限要求

SpringBoot返回文件让前端下载的几种方式

《SpringBoot返回文件让前端下载的几种方式》文章介绍了开发中文件下载的两种常见解决方案,并详细描述了通过后端进行下载的原理和步骤,包括一次性读取到内存和分块写入响应输出流两种方法,此外,还提供... 目录01 背景02 一次性读取到内存,通过响应输出流输出到前端02 将文件流通过循环写入到响应输出流

前端Visual Studio Code安装配置教程之下载、汉化、常用组件及基本操作

《前端VisualStudioCode安装配置教程之下载、汉化、常用组件及基本操作》VisualStudioCode是微软推出的一个强大的代码编辑器,功能强大,操作简单便捷,还有着良好的用户界面,... 目录一、Visual Studio Code下载二、汉化三、常用组件1、Auto Rename Tag2

Python包管理工具uv下载python版本慢问题解决办法

《Python包管理工具uv下载python版本慢问题解决办法》uv是一个非常快的Python包和项目管理器,用Rust编写,使用热缓存安装Trio的依赖项的速度对比,:本文主要介绍Python包... 目录发现问题对于 MACOS / linux 用户 (zsh/bash):对于 Windows 用户:总

Python实现简单封装网络请求的示例详解

《Python实现简单封装网络请求的示例详解》这篇文章主要为大家详细介绍了Python实现简单封装网络请求的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录安装依赖核心功能说明1. 类与方法概览2.NetHelper类初始化参数3.ApiResponse类属性与方法使用实

SpringBoot 获取请求参数的常用注解及用法

《SpringBoot获取请求参数的常用注解及用法》SpringBoot通过@RequestParam、@PathVariable等注解支持从HTTP请求中获取参数,涵盖查询、路径、请求体、头、C... 目录SpringBoot 提供了多种注解来方便地从 HTTP 请求中获取参数以下是主要的注解及其用法:1

Debian 13升级后网络转发等功能异常怎么办? 并非错误而是管理机制变更

《Debian13升级后网络转发等功能异常怎么办?并非错误而是管理机制变更》很多朋友反馈,更新到Debian13后网络转发等功能异常,这并非BUG而是Debian13Trixie调整... 日前 Debian 13 Trixie 发布后已经有众多网友升级到新版本,只不过升级后发现某些功能存在异常,例如网络转

SpringBoot请求参数传递与接收示例详解

《SpringBoot请求参数传递与接收示例详解》本文给大家介绍SpringBoot请求参数传递与接收示例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋... 目录I. 基础参数传递i.查询参数(Query Parameters)ii.路径参数(Path Va

使用Python的requests库来发送HTTP请求的操作指南

《使用Python的requests库来发送HTTP请求的操作指南》使用Python的requests库发送HTTP请求是非常简单和直观的,requests库提供了丰富的API,可以发送各种类型的HT... 目录前言1. 安装 requests 库2. 发送 GET 请求3. 发送 POST 请求4. 发送

Python多线程实现大文件快速下载的代码实现

《Python多线程实现大文件快速下载的代码实现》在互联网时代,文件下载是日常操作之一,尤其是大文件,然而,网络条件不稳定或带宽有限时,下载速度会变得很慢,本文将介绍如何使用Python实现多线程下载... 目录引言一、多线程下载原理二、python实现多线程下载代码说明:三、实战案例四、注意事项五、总结引