ZFRetryDownloader

一个基于NSURLSession实现的下载模块,并封装了一个下载失败重试的逻辑。

Getting Started

These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.

Xcode 9.0 or later; iOS 9.0 SDK or later

Installing

首先从GitHub上克隆项目

git clone https://github.com/zhonglaoban/ZFRetryDownloader.git

然后打开ZFRetryDownloader.xcodeproj,就可以运行啦。

Running the project

运行项目你会看到这个样子

  • 图1:下载失败后无法显示图片
  • 图2:下载失败会根据设置的数组重试或者下载其他资源

Code Analysis

这个下载器是基于NSURLSession实现的,简单的说一下实现原理。

单例

在面向对象编程中,我们的下载器不需要频繁的创建和销毁,我们只需要管理好每一个下载的Task即可,所以我们用单例实现这个下载器。
注意:在oc中,init是初始化类的属性,alloc才是分配内存空间,所以我们需要对allocWithZone、init都做处理,确保是一个实例。

+ (instancetype)shared {return [[ZFDownloader alloc] init];
}
- (instancetype)init {if (instance == nil) {dispatch_once(&onceToken, ^{instance = [super init];});}return instance;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {if (instance == nil) {dispatch_once(&onceToken, ^{instance = [super allocWithZone:zone];});}return instance;
}

我们创建一个task,然后开始下载。一个task就是一个下载任务,我们需要对不同的任务进行处理,所以我们创建一个ZFTaskDelegate类来处理这些下载任务。

ZFTaskDelegate *delegate = [[ZFTaskDelegate alloc] init];NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:delegate delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[NSURL URLWithString:url]];
[task resume];

将一些回调传递给delegate,通知调用者。

delegate.recievedProgressBlock = progressBlock;
delegate.downloadSuccessfulBlock = success;
delegate.downloadFailedBlock = failure;
delegate.savePath = savedPath;

接下来处理delegate中的事情,由于NSURLSessionDownloadDelegate只有下载完成、下载进度的回调,我们的代理还需要实现NSURLSessionTaskDelegate的协议,来处理异常的情况。

下载完成

下载完成后,文件默认存放在tmp文件夹中,会被系统清除,所以我们需要把文件拷贝到其他目录中。

- (void)URLSession:(nonnull NSURLSession *)session downloadTask:(nonnull NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(nonnull NSURL *)location {NSFileManager *fileManager = [NSFileManager defaultManager];if ([fileManager fileExistsAtPath:self.savePath] == NO){if (self.downloadFailedBlock) {NSError *pathError = [NSError errorWithDomain:NSCocoaErrorDomain code: -1 userInfo:@{NSLocalizedFailureReasonErrorKey: @"目标路径不存在"}];self.downloadFailedBlock(pathError);}return;}NSURL *destUrl = [NSURL fileURLWithPath:[self.savePath stringByAppendingPathComponent:downloadTask.response.suggestedFilename]];NSError *fileMoveError;if ([fileManager fileExistsAtPath:destUrl.path]){if (self.downloadSuccessfulBlock) {self.downloadSuccessfulBlock(destUrl);}return;}BOOL result = [fileManager moveItemAtURL:location toURL:destUrl error:&fileMoveError];if (result) {if (self.downloadSuccessfulBlock) {self.downloadSuccessfulBlock(destUrl);}}else {if (self.downloadFailedBlock) {self.downloadFailedBlock(fileMoveError);}}
}

下载进度计算

下载进度计算是根据当前下载数据的大小比上下载数据的大小

-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;if (self.recievedProgressBlock) {self.recievedProgressBlock(progress);}
}

错误处理

当下载出错时会走didCompleteWithError这个方法。

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {if (error) {if (self.downloadFailedBlock) {self.downloadFailedBlock(error);}}
}

下载重试逻辑

下载失败后我们希望自动重试,或者去下载其他资源,比如A->A->B、A->A->A->C等这样的逻辑。我的思路是把这些任务放在一个队列中,一次执行一个任务,如果有一个任务成功了,后面的就不执行了,这里我用的是GCD的信号量来控制的。

- (void)retryDownloadFileWithUrls:(NSArray<NSString *> *)urls savedPath:(NSString* _Nonnull)savedPath progress:(void (^_Nullable)(float progress))progressBlock success:(void (^ _Nullable )(NSURL * _Nonnull location))successBlock failure:(void (^ _Nonnull )(NSError * _Nonnull error))failureBlock {__block BOOL shouldRetry = YES;dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);dispatch_async(dispatch_queue_create("com.zf.retryDownloadFileWithUrls", DISPATCH_QUEUE_SERIAL), ^{for (NSString *url in urls) {dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);if (shouldRetry == NO) {dispatch_semaphore_signal(semaphore);return;}[self downloadFileWithUrl:url savedPath:savedPath progress:^(float progress) {if (progressBlock) {progressBlock(progress);}} success:^(NSURL * _Nonnull location) {shouldRetry = NO;dispatch_semaphore_signal(semaphore);if (successBlock) {successBlock(location);}} failure:^(NSError * _Nonnull error) {dispatch_semaphore_signal(semaphore);if (failureBlock) {failureBlock(error);}}];}});
}

How to use

使用普通下载

- (IBAction)normalDownload:(id)sender {NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);NSString *savedPath = [paths objectAtIndex:0];[[ZFDownloader shared] downloadFileWithUrl:[self.retryUrls firstObject] savedPath:savedPath progress:^(float progress) {NSLog(@"%f", progress);} success:^(NSURL * _Nonnull location) {self.imageView.image = [UIImage imageWithContentsOfFile:location.path];} failure:^(NSError * _Nonnull error) {NSLog(@"%@", error);}];
}

使用重试下载

- (IBAction)retryDownload:(id)sender {NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);NSString *savedPath = [paths objectAtIndex:0];[[ZFDownloader shared] retryDownloadFileWithUrls:self.retryUrls savedPath:savedPath progress:^(float progress) {NSLog(@"%f", progress);} success:^(NSURL * _Nonnull location) {self.imageView.image = [UIImage imageWithContentsOfFile:location.path];} failure:^(NSError * _Nonnull error) {NSLog(@"%@", error);}];
}

github地址

Authors

  • zhonglaoban - Initial work - zhonglaoban

License

This project is licensed under the MIT License - see the LICENSE.md file for details

基于NSURLSession写一个下载工具相关推荐

  1. 学了C语言,如何利用CURL写一个下载程序?—用nmake编译CURL并安装

    在这一系列的前一篇文章学了C语言,如何为下载狂人写一个磁盘剩余容量监控程序?中,我们为下载狂人写了一个程序来监视磁盘的剩余容量,防止下载的东西撑爆了硬盘.可是,这两天,他又抱怨他的下载程序不好用,让我 ...

  2. 【vite+vue3.0】基于vite写一个将md文件渲染为js文件的插件

    基于vite写一个将md文件渲染为js文件的插件 前言 尤大是这么描述 Vite 的: 「一个基于浏览器原生 ES imports 的开发服务器. 利用浏览器去解析 imports,在服务器端按需编译 ...

  3. 【python小项目】用python写一个小工具——番茄钟

    用python写一个小工具--番茄钟 最近听到朋友说在用番茄钟,有点兴趣也想下载一个来用用,后面仔细一想这玩意做起来也不难,索性自己顺手写一个算了,在这里也分享给大家了 一.功能简述 番茄钟即番茄工作 ...

  4. You-Get——基于Python3的媒体下载工具

    You-Get是一个基于 Python 3 的下载工具.使用 You-Get 可以很轻松的下载到网络上的视频.图片及音乐. 项目主页:https://github.com/soimort/you-ge ...

  5. python编写测试工具-python 写一个性能测试工具(一)

    国庆重新学习了一下go的gin高性能测试框架. 用JMeter来测试gin与flask接口的性能,差别很大. 为什么我自己不尝试写一个性能工具,性能工具的核心就是 并发 和 请求. 请求可以选择Pyt ...

  6. python 性能测试_python 写一个性能测试工具(一)

    国庆重新学习了一下go的gin高性能测试框架. 用JMeter来测试gin与flask接口的性能,差别很大. 为什么我自己不尝试写一个性能工具,性能工具的核心就是 并发 和 请求. 请求可以选择Pyt ...

  7. python kivy显示图片_python基于Kivy写一个图形桌面时钟程序代码示例

    本篇文章小编给大家分享一下python基于Kivy写一个图形桌面时钟程序代码示例,文章代码介绍的很详细,小编觉得挺不错的,现在分享给大家供大家参考,有需要的小伙伴们可以来看看. Kivy 是一个开源的 ...

  8. python时钟程序的设计总结_python基于Kivy写一个图形桌面时钟程序

    kivy 是一个开源的 python 第三方库,可以用来快速开发应用程序. 它有如下三个特点: 跨平台 kivy 编写的程序可在 linux,windows,os x,android,ios 和 ra ...

  9. WPF 写一个提醒工具软件(完整项目)

    昨天整理硬盘时,偶然发现一个很久之前写的小工具,一个提醒工具. 包含定时提醒,间隔提醒功能. 看看效果: 界面看起来也还凑合,还使用了HandyControl,有桌面托盘功能 界面是下面这样的 提醒窗 ...

最新文章

  1. Bi-level error correction for PacBio long reads. PacBio长读数的两级纠错
  2. linux c/c++ 判断是否为中文(不包括中文符号,非正则)
  3. unity3d干货分享:实现敌人锥形视角的3个方法
  4. 2018上C语言程序设计(高级)作业-第0次作业
  5. C#3.0中的扩展方法
  6. 1106 Lowest Price in Supply Chain(25 分)
  7. 7.2.5 dps 测试软件,魔兽世界7.2.5兽王猎DPS有什么改动测试
  8. 关键字:c++builder(BCB) C# WebService EAccessViolation
  9. C++之函数后面加const
  10. 最新大数据产业生态地图:十大爆发点,百大公司起底
  11. 011 索引的优点,特大型的表考虑分区技术
  12. JQuery之UI插件
  13. 不叹惜、不呼唤我也不哭泣
  14. python正则表达式aabb式成语_python——正则表达式
  15. layUI 表格中1:0转换成男女 if else数字转对应中文显示
  16. 逐鹿量子计算,“先导杯”向世界难题发起冲击!
  17. linux安装jdk以及单独安装jre
  18. 短视频是屌丝逆袭的一个绝好的机会
  19. 计算免息分期的收益 及 年金/复利现值终值的理解
  20. #劲舞团# 熬点小米稀饭喝,有六年没有喝过了。确实是家长的味道。

热门文章

  1. VR全景怎么赚钱?结合市场从两个方面客观分析下
  2. 认知症养老进入下半场:历程回顾、盈利困境、创新模式、未来展望
  3. 西部数码php.ini,西部数码虚拟主机提权思路
  4. H264 NALU说明
  5. android 屏幕投射_将自定义内容从Android应用投射到电视(2020年方法)
  6. 《The Chubby lock service for loosely-coupled distributed systems》论文阅读
  7. Police Station
  8. centos6.5 mysql 远程访问_CentOS 6.5 中安装 Mysql 5.6,并远程连接Mysql
  9. 古巨基与女助理结束20年恋爱长跑 或于今秋结婚
  10. (C语言)模拟实现库函数strstr()