iOS开发之网络编程--4、NSURLSessionDataTask实现文件下载(离线断点续传下载) <进度值显示优化>

简介:

前言:根据前篇《iOS开发之网络编程--2、NSURLSessionDownloadTask文件下载》或者《iOS开发之网络编程--3、NSURLSessionDataTask实现文件下载(离线断点续传下载)》,都遗留了一个细节未处理的问题,那就是在离线断点下载的过程中,当应用程序重新启动之后,进度条的进度值默认没有设置为之前已经下载的进度,根据基本公式"当前进度值 = 已经下载的数据长度 ÷ 最终下载完的数据总长度",已经下载的数据长度可以由沙盒中已经下载的那部分数据获取,但是最终下载完的数据总长度就需要通过网络返回的信息了,但是别忘了,每一次重新启动应用程序初始状态默认都是暂停下载,或者是断网的情况下无法请求网络数据,那么如何获取这个"最终下载完的数据总长度"呢?

本篇还涉及到在子线程创建下载任务,然后通过线程通知UI主线程更新进度条控件显示进度。因为delegateQueue这个属性可以设置主队列线程或者是子队列线程。

 

先看看效果:

问题解决:"最终下载完的数据总长度"可以在首次从0开始下载的时候通过网络获取,然后将其"最终下载完的数据总长度"这个值存储在缓存中的某个文件(这个文件可以是字典等等)中,等待下一次获取。

       而我则采用的方法是将这个"最终下载完的数据总长度"作为文件的属性添加进文件属性列表中,以备下一次读取的时候,获得到这个文件之后,就可以读取该文件的属性列表中的"最终下载完

     的数据总长度"的属性和属性值。

在这里不得不先介绍一个工具类,读者可以通过本人的另一篇博文随笔先了解其功能:iOS开发 -- 为本地文件添加自定义属性的工具类

为本地文件添加属性之后,可以打印看的到:

本人花了点时间将网络下载这部分简单的封装成了一个工具类:DownloadTool,下面就展示源码:

DownloadTool.h

#import <Foundation/Foundation.h>


// 定义一个block用来传递进度值
typedef  void (^SetProgressValue)(float progressValue);

@interface DownloadTool : NSObject

/** 创建下载工具对象 */
+ (instancetype)DownloadWithURLString:(NSString*)urlString setProgressValue:(SetProgressValue)setProgressValue;
/** 开始下载 */
-(void)startDownload;
/** 暂停下载 */
-(void)suspendDownload;

@end

DownloadTool.m
#import "DownloadTool.h"

#import "ExpendFileAttributes.h"

#define Key_FileTotalSize @"Key_FileTotalSize"

@interface DownloadTool () <NSURLSessionDataDelegate>
/** Session会话 */
@property (nonatomic,strong)NSURLSession *session;
/** Task任务 */
@property (nonatomic,strong)NSURLSessionDataTask *task;
/** 文件的全路径 */
@property (nonatomic,strong)NSString *fileFullPath;
/** 传递进度值的block */
@property (nonatomic,copy) SetProgressValue setProgressValue;
/** 当前已经下载的文件的长度 */
@property (nonatomic,assign)NSInteger currentFileSize;
/** 输出流 */
@property (nonatomic,strong)NSOutputStream *outputStream;
/** 不变的文件总长度 */
@property (nonatomic,assign)NSInteger fileTotalSize;
@end

@implementation DownloadTool

+ (instancetype)DownloadWithURLString:(NSString*)urlString setProgressValue:(SetProgressValue)setProgressValue{
    DownloadTool* download = [[DownloadTool alloc] init];
    download.setProgressValue = setProgressValue;
    [download getFileSizeWithURLString:urlString];
    [download creatDownloadSessionTaskWithURLString:urlString];
    NSLog(@"%@",download.fileFullPath);
    return download;
}
// 刚创建该网络下载工具类的时候,就需要查询本地是否有已经下载的文件,并返回该文件已经下载的长度
-(void)getFileSizeWithURLString:(NSString*)urlString{
    // 创建文件管理者
    NSFileManager* fileManager = [NSFileManager defaultManager];
    // 获取文件各个部分
    NSArray* fileComponents = [fileManager componentsToDisplayForPath:urlString];
    // 获取下载之后的文件名
    NSString* fileName = [fileComponents lastObject];
    // 根据文件名拼接沙盒全路径
    NSString* fileFullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:fileName];
    self.fileFullPath = fileFullPath;
    
    NSDictionary* attributes = [fileManager attributesOfItemAtPath:fileFullPath
                                                             error:nil];
    // 如果有该文件,且为下载没完成,就直接拿出该文件的长度设置进度值,并设置当前的文件长度
    NSInteger fileCurrentSize = [attributes[@"NSFileSize"] integerValue];
    // 如果文件长度为0,就不需要计算进度值了
    if (fileCurrentSize != 0) {
        // 获取最终的文件中长度
        NSInteger fileTotalSize = [[ExpendFileAttributes stringValueWithPath:self.fileFullPath key:Key_FileTotalSize] integerValue];
        self.currentFileSize = fileCurrentSize;
        self.fileTotalSize = fileTotalSize;
        // 设置进度条的值
        self.setProgressValue(1.0 * fileCurrentSize / fileTotalSize);
    }
    NSLog(@"当前文件长度:%lf" , self.currentFileSize * 1.0);
}
#pragma mark - 创建网络请求会话和任务,并启动任务
-(void)creatDownloadSessionTaskWithURLString:(NSString*)urlString{
    //判断文件是否已经下载完毕
    if (self.currentFileSize == self.fileTotalSize && self.currentFileSize != 0) {
        NSLog(@"文件已经下载完毕");
        return;
    }
    NSURLSession* session =
    [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
                                  delegate:self
                             delegateQueue:[[NSOperationQueue alloc]init]];
    NSURL* url = [NSURL URLWithString:urlString];
    NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
    //2.3 设置请求头
    NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentFileSize];
    [request setValue:range forHTTPHeaderField:@"Range"];
    NSURLSessionDataTask* task = [session dataTaskWithRequest:request];
    self.session = session;
    self.task = task;
}

#pragma mark - 控制下载的状态
// 开始下载
-(void)startDownload{
    [self.task resume];
}
// 暂停下载
-(void)suspendDownload{
    [self.task suspend];
}
#pragma mark - NSURLSessionDataDelegate 的代理方法
// 收到响应调用的代理方法
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:
(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
    NSLog(@"执行了收到响应调用的代理方法");
    // 创建输出流,并打开流
    NSOutputStream* outputStream = [[NSOutputStream alloc] initToFileAtPath:self.fileFullPath append:YES];
    [outputStream open];
    self.outputStream = outputStream;
    // 如果当前已经下载的文件长度等于0,那么就需要将总长度信息写入文件中
    if (self.currentFileSize == 0) {
        NSInteger totalSize = response.expectedContentLength;
        NSString* totalSizeString = [NSString stringWithFormat:@"%ld",totalSize];
        [ExpendFileAttributes extendedStringValueWithPath:self.fileFullPath key:Key_FileTotalSize value:totalSizeString];
        // 别忘了设置总长度
        self.fileTotalSize = totalSize;
    }
    // 允许收到响应
    completionHandler(NSURLSessionResponseAllow);
}
// 收到数据调用的代理方法
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    NSLog(@"执行了收到数据调用的代理方法");
    // 通过输出流写入数据
    [self.outputStream write:data.bytes maxLength:data.length];
    // 将写入的数据的长度计算加进当前的已经下载的数据长度
    self.currentFileSize += data.length;
    // 设置进度值
    NSLog(@"当前文件长度:%lf,总长度:%lf",self.currentFileSize * 1.0,self.fileTotalSize * 1.0);
    NSLog(@"进度值: %lf",self.currentFileSize * 1.0 / self.fileTotalSize);
    // 获取主线程
    NSOperationQueue* mainQueue = [NSOperationQueue mainQueue];
    [mainQueue addOperationWithBlock:^{
        self.setProgressValue(self.currentFileSize * 1.0 / self.fileTotalSize);
    }];
}
// 数据下载完成调用的方法
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    // 关闭输出流 并关闭强指针
    [self.outputStream close];
    self.outputStream = nil;
    // 关闭会话
    [self.session invalidateAndCancel];
    NSLog(@"%@",[NSThread currentThread]);
}
-(void)dealloc{
}
@end

使用示例源码:
#import "ViewController.h"
#import "RainbowProgress.h"

#import "DownloadTool.h"

#define MP4_URL_String @"http://120.25.226.186:32812/resources/videos/minion_12.mp4"


@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *showDownloadState;
/** 彩虹进度条 */
@property (nonatomic,weak)RainbowProgress *rainbowProgress;
/** 网络下载工具对象 */
@property (nonatomic,strong)DownloadTool *download;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setSelfView];
    [self addProgress];
    [self addDownload];
    
}
// 启动和关闭的网络下载开关
- (IBAction)SwitchBtn:(UISwitch *)sender {
    if (sender.isOn) {
        self.showDownloadState.text = @"开始下载";
        [self.download startDownload];
    }else{
        self.showDownloadState.text = @"暂停下载";
        [self.download suspendDownload];
    }
}
#pragma mark - 设置控制器View
-(void)setSelfView{
    self.view.backgroundColor = [UIColor blackColor];
}
#pragma mark - 添加彩虹进度条
-(void)addProgress{
    // 创建彩虹进度条,并启动动画
    RainbowProgress* rainbowProgress = [[RainbowProgress alloc] init];
    [rainbowProgress startAnimating];
    [self.view addSubview:rainbowProgress];
    self.rainbowProgress = rainbowProgress;
}
#pragma mark - 创建网络下载任务
-(void)addDownload{
    DownloadTool* download = [DownloadTool DownloadWithURLString:MP4_URL_String setProgressValue:^(float progressValue) {
        self.rainbowProgress.progressValue = progressValue;
    }];
    self.download = download;
}

#pragma mark - 设置状态栏样式
-(UIStatusBarStyle)preferredStatusBarStyle{
    return UIStatusBarStyleLightContent;
}

@end

效果图(中间有个过程是重新启动应用程序看看进度条显示的效果,然后继续测试开始下载和暂停下载):

 

百度云分享源码链接: http://pan.baidu.com/s/1eRwRkZo 密码: 787n

相关文章
|
23天前
|
JSON 中间件 Go
Go 网络编程:HTTP服务与客户端开发
Go 语言的 `net/http` 包功能强大,可快速构建高并发 HTTP 服务。本文从创建简单 HTTP 服务入手,逐步讲解请求与响应对象、URL 参数处理、自定义路由、JSON 接口、静态文件服务、中间件编写及 HTTPS 配置等内容。通过示例代码展示如何使用 `http.HandleFunc`、`http.ServeMux`、`http.Client` 等工具实现常见功能,帮助开发者掌握构建高效 Web 应用的核心技能。
157 61
|
9月前
|
Linux 开发工具 Android开发
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
ijkplayer是由Bilibili基于FFmpeg3.4研发并开源的播放器,适用于Android和iOS,支持本地视频及网络流媒体播放。本文详细介绍如何在新版Android Studio中导入并使用ijkplayer库,包括Gradle版本及配置更新、导入编译好的so文件以及添加直播链接播放代码等步骤,帮助开发者顺利进行App调试与开发。更多FFmpeg开发知识可参考《FFmpeg开发实战:从零基础到短视频上线》。
889 2
FFmpeg开发笔记(六十)使用国产的ijkplayer播放器观看网络视频
|
4月前
|
网络协议 物联网
VB6网络通信软件上位机开发,TCP网络通信,读写数据并处理,完整源码下载
本文介绍使用VB6开发网络通信上位机客户端程序,涵盖Winsock控件的引入与使用,包括连接服务端、发送数据(如通过`Winsock1.SendData`方法)及接收数据(利用`Winsock1_DataArrival`事件)。代码实现TCP网络通信,可读写并处理16进制数据,适用于自动化和工业控制领域。提供完整源码下载,适合学习VB6网络程序开发。 下载链接:[完整源码](http://xzios.cn:86/WJGL/DownLoadDetial?Id=20)
143 12
|
8月前
|
API
鸿蒙开发:切换至基于rcp的网络请求
本文的内容主要是把之前基于http封装的库,修改为当前的Remote Communication Kit(远场通信服务),无非就是通信的方式变了,其他都大差不差。
211 4
鸿蒙开发:切换至基于rcp的网络请求
|
8月前
|
存储 网络协议 物联网
C 语言物联网开发之网络通信与数据传输难题
本文探讨了C语言在物联网开发中遇到的网络通信与数据传输挑战,分析了常见问题并提出了优化策略,旨在提高数据传输效率和系统稳定性。
|
7月前
|
SQL 安全 网络安全
网络安全与信息安全:知识分享####
【10月更文挑战第21天】 随着数字化时代的快速发展,网络安全和信息安全已成为个人和企业不可忽视的关键问题。本文将探讨网络安全漏洞、加密技术以及安全意识的重要性,并提供一些实用的建议,帮助读者提高自身的网络安全防护能力。 ####
178 17
|
7月前
|
SQL 安全 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
随着互联网的普及,网络安全问题日益突出。本文将从网络安全漏洞、加密技术和安全意识三个方面进行探讨,旨在提高读者对网络安全的认识和防范能力。通过分析常见的网络安全漏洞,介绍加密技术的基本原理和应用,以及强调安全意识的重要性,帮助读者更好地保护自己的网络信息安全。
133 10
|
7月前
|
存储 SQL 安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
随着互联网的普及,网络安全问题日益突出。本文将介绍网络安全的重要性,分析常见的网络安全漏洞及其危害,探讨加密技术在保障网络安全中的作用,并强调提高安全意识的必要性。通过本文的学习,读者将了解网络安全的基本概念和应对策略,提升个人和组织的网络安全防护能力。
|
7月前
|
SQL 安全 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
在数字化时代,网络安全和信息安全已成为我们生活中不可或缺的一部分。本文将介绍网络安全漏洞、加密技术和安全意识等方面的内容,并提供一些实用的代码示例。通过阅读本文,您将了解到如何保护自己的网络安全,以及如何提高自己的信息安全意识。
152 10
|
7月前
|
监控 安全 网络安全
网络安全与信息安全:漏洞、加密与意识的交织
在数字时代的浪潮中,网络安全与信息安全成为维护数据完整性、保密性和可用性的关键。本文深入探讨了网络安全中的漏洞概念、加密技术的应用以及提升安全意识的重要性。通过实际案例分析,揭示了网络攻击的常见模式和防御策略,强调了教育和技术并重的安全理念。旨在为读者提供一套全面的网络安全知识框架,从而在日益复杂的网络环境中保护个人和组织的资产安全。

热门文章

最新文章