iOS中的下载管理器(支持断点续传)
在空闲时间自己编写了一个简单的iOS下载管理器。该管理器实现如下功能:
1、能够支持正常的下载,暂停,继续操作。
2、支持断点续传,实现暂停执行继续操作后,依然能正常将文件下载完成。
3、实现实时状态回调,下载进度,速度,一目了然。
准备工作:压缩文件
遇到的主要问题: 拼接到内存中的数据峰值太大,会导致app闪退.
解决办法:
一.(1)用NSFileHandle解决占用内存过大问题(下载一点 写入沙盒一点)
#import "ViewController.h"
@interface ViewController ()<NSURLConnectionDataDelegate>
@property(nonatomic,copy)NSString *filePath;
/**
* 服务器上面的文件的总大小
*/
@property(nonatomic,assign)long long expectedContentLength;
/**
* 已经接收到的文件的大小
*/
@property(nonatomic,assign)long long hasReceivedContentLength;
/**
* 文件的管道
*/
@property(nonatomic,strong)NSFileHandle *writeFileHandle;
@end
@implementation ViewController
#pragma mark - 懒加载
- (NSString *)filePath{
if (_filePath==nil) {
//获取到Cache目录
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
_filePath = [cache stringByAppendingPathComponent:@"dh.zip"];
}
return _filePath;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSLog(@"%@",NSHomeDirectory());
}
/**
这个地方不太好
1.没有进度
2.进度峰值太大,会导致我们的app闪退
*/
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//1.URL
NSURL *url = [NSURL URLWithString:@"http://localhost/video.zip"];
//2.NSURLRequest
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.只要调用了类方法就会自动发起请求
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
}
#pragma mark - DownLoadDelegate
/*
这个只会调用一次
*/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(@"下载开始!!!");
self.expectedContentLength = response.expectedContentLength;
//1.创建一个0KB的文件,空文件
/**
NSFileManger 做大事,创建,删除,移动我们的文件,并且还可以获取文件的信息
NSFileHandle 做小事,专门用来,流入数据
*/
NSFileManager *manager = [NSFileManager defaultManager];
BOOL createSuccess = [manager createFileAtPath:self.filePath contents:nil attributes:NULL];
//2.创建管道
if (createSuccess) {
NSLog(@"创建文件成功!!!");
self.writeFileHandle = [NSFileHandle fileHandleForWritingAtPath:self.filePath];
}
}
/**
这个玩意儿会调用多次
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
//NSLog(@"%zd",data.length);
//1.拼接文件的总大小
self.hasReceivedContentLength += data.length;
float progress = (float)self.hasReceivedContentLength / (float)self.expectedContentLength;
NSLog(@"progress==%f",progress);
//2.接收到一点,就写入到沙盒指定的文件里面去一点
//2.1把文件指针,移到已经下载完毕的文件的末尾
[self.writeFileHandle seekToEndOfFile];
//2.2 将新获取到的data 拼接到已经下载完毕的文件的末尾
[self.writeFileHandle writeData:data];
}
/**
只会调用一次
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(@"下载完毕!!!");
//需要将管道关闭
[self.writeFileHandle closeFile];
}
@end
(2)用NSOutputStream解决占用内存过大问题
#import "ViewController.h"
@interface ViewController ()<NSURLConnectionDataDelegate>
@property(nonatomic,copy)NSString *filePath;
/**
* 服务器上面的文件的总大小
*/
@property(nonatomic,assign)long long expectedContentLength;
/**
* 已经接收到的文件的大小
*/
@property(nonatomic,assign)long long hasReceivedContentLength;
/**
* 输出,输入是以内存为参照物的
输出,就是将内存中的东西,输出到沙盒
输入,就是将沙盒中的东西输入到内存
*/
@property(nonatomic,strong)NSOutputStream *outputStream;
@end
@implementation ViewController
#pragma mark - 懒加载
- (NSString *)filePath{
if (_filePath==nil) {
//获取到Cache目录
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
_filePath = [cache stringByAppendingPathComponent:@"dh.zip"];
}
return _filePath;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSLog(@"%@",NSHomeDirectory());
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"%s",__func__);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//1.URL
NSURL *url = [NSURL URLWithString:@"http://localhost/video.zip"];
//2.NSURLRequest
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.只要调用了类方法就会自动发起请求
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
//开启子线程的NSRunloop,下载比较特殊,只要下载完毕之后,他的NSRunloop会自动停止
[[NSRunLoop currentRunLoop] run];
});
}
#pragma mark - DownLoadDelegate
/*
这个只会调用一次
*/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(@"下载开始!!!");
self.expectedContentLength = response.expectedContentLength;
//1.创建输出流
self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.filePath append:YES];
//2.打开流
#warning 打开流
[self.outputStream open];
}
/**
这个玩意儿会调用多次
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
//NSLog(@"%zd",data.length);
//1.拼接文件的总大小
self.hasReceivedContentLength += data.length;
float progress = (float)self.hasReceivedContentLength / (float)self.expectedContentLength;
NSLog(@"progress==%f %@",progress,[NSThread currentThread]);
//获取一点,就写入沙盒一点
[self.outputStream write:data.bytes maxLength:data.length];
}
/**
只会调用一次
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(@"下载完毕!!!");
//关闭流
[self.outputStream close];
}
@end
二.文件下载进度(暂停和恢复)
#import "ViewController.h"
@interface ViewController ()<NSURLConnectionDataDelegate>
@property(nonatomic,copy)NSString *filePath;
/**
* 服务器上面的文件的总大小
*/
@property(nonatomic,assign)long long expectedContentLength;
/**
* 已经接收到的文件的大小
*/
@property(nonatomic,assign)long long hasReceivedContentLength;
/**
* 输出,输入是以内存为参照物的
输出,就是将内存中的东西,输出到沙盒
输入,就是将沙盒中的东西输入到内存
*/
@property(nonatomic,strong)NSOutputStream *outputStream;
/**
* connection
*/
@property(nonatomic,strong)NSURLConnection *connection;
@end
@implementation ViewController
#pragma mark - 懒加载
- (NSString *)filePath{
if (_filePath==nil) {
//获取到Cache目录
NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
_filePath = [cache stringByAppendingPathComponent:@"dh.zip"];
}
return _filePath;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%@",NSHomeDirectory());
}
//开始下载
- (IBAction)start:(id)sender {
NSLog(@"%s",__func__);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//获取到我们的文件的总大小
[self getFileLenght];
//1.URL
NSURL *url = [NSURL URLWithString:@"http://localhost/video.zip"];
//2.NSURLRequest
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.只要调用了类方法就会自动发起请求
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
//开启我们子线程的NSRunloop,下载比较特殊,只要下载完毕之后,他的NSRunloop会自动停止
[[NSRunLoop currentRunLoop] run];
});
}
//暂停
- (IBAction)pause:(id)sender {
//取消
//一旦调用了这个方法,connnection就废了
[self.connection cancel];
}
//恢复
- (IBAction)resume:(id)sender {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//先得到文件的大小
[self getFileLenght];
//1.URL
NSURL *url = [NSURL URLWithString:@"http://localhost/video.zip"];
//2.NSURLRequest
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//2.1 设置请求头,告诉服务器一些额外的信息
NSString *rangeValue = [NSString stringWithFormat:@"bytes=%lld-",self.hasReceivedContentLength];
[request setValue:rangeValue forHTTPHeaderField:@"Range"];
//3.只要调用了类方法就会自动发起请求
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
//开启我们子线程的NSRunloop,下载比较特殊,只要下载完毕之后,他的NSRunloop会自动停止
[[NSRunLoop currentRunLoop] run];
});
}
- (void)getFileLenght{
//1.URL
NSURL *url = [NSURL URLWithString:@"http://localhost/video.zip"];
//2.NSURLRequest
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//如果只是获取文件的信息,而不是把数据download下来
request.HTTPMethod = @"HEAD";
//3.发送请求去服务器上面,获取文件的信息
NSURLResponse *response;
NSData *data =[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
NSLog(@"getFileLenght %zd",data.length);
//4.设置给我们文件的总长度
self.expectedContentLength = response.expectedContentLength;
NSLog(@"文件的总大小 %zd",self.expectedContentLength);
}
#pragma mark - DownLoadDelegate
/*
这个只会调用一次
*/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(@"下载开始!!!");
NSLog(@"文件总大小----------%lld",response.expectedContentLength);
//1.创建输出流
self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.filePath append:YES];
//2.打开流
#warning 打开流
[self.outputStream open];
}
/**
这个玩意儿会调用多次
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
//NSLog(@"%zd",data.length);
//1.拼接文件的总大小
self.hasReceivedContentLength += data.length;
float progress = (float)self.hasReceivedContentLength / (float)self.expectedContentLength;
NSLog(@"progress==%f %@",progress,[NSThread currentThread]);
//获取一点,就写入沙盒一点
[self.outputStream write:data.bytes maxLength:data.length];
}
/**
只会调用一次
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(@"下载完毕!!!");
//关闭流
[self.outputStream close];
}
@end
三.封装进度条控件
.h文件
/**
XCode6开始,可以动态的在我们Xib里面调试
*/
#import <UIKit/UIKit.h>
IB_DESIGNABLE
@interface ProgressView : UIView
/**
进度必须在0到1之间
*/
@property(nonatomic,assign) IBInspectable float progress;
/**
* 外环
*/
@property(nonatomic,assign) IBInspectable CGFloat waiHuanLineWidth;
@property(nonatomic,strong) IBInspectable UIColor *huanColor;
@end
.m文件
#import "ProgressView.h"
@implementation ProgressView
- (void)setProgress:(float)progress{
_progress = progress;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
//得到宽度和高度
CGFloat width = rect.size.width;
CGFloat height = rect.size.height;
//1.中点
CGPoint centerPoint = CGPointMake(width *0.5, height*0.5);
//2.外环
//7.线框
if (self.waiHuanLineWidth==0) {
self.waiHuanLineWidth = 5;
}
//3.半径
CGFloat waiHuanRadius = width * 0.5 - self.waiHuanLineWidth*0.5;
//4.画圆
UIBezierPath *waiHuanBezierPath = [UIBezierPath bezierPathWithArcCenter:centerPoint radius:waiHuanRadius startAngle:0 endAngle:M_PI*2 clockwise:YES];
//5.颜色
if (self.huanColor == nil) {
self.huanColor = [UIColor blueColor];
}
//6.设置颜色
[self.huanColor set];
//7.设置外环的宽度
waiHuanBezierPath.lineWidth = self.waiHuanLineWidth;
//8.画出来
[waiHuanBezierPath stroke];
//画矩形
CGFloat cubeWidth = 20;
CGFloat cubeHeight = 20;
CGFloat cubeX = width *0.5 - cubeWidth*0.5;
CGFloat cubeY = height *0.5 - cubeHeight*0.5;
UIBezierPath *cubeBezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(cubeX, cubeY, cubeWidth, cubeHeight)];
[self.huanColor set];
//画出来我们中间的矩形
[cubeBezierPath fill];
//画那个动的圆形
CGFloat neiYuanWidth = self.waiHuanLineWidth;
CGFloat neiYuanRadius = width *0.5 - self.waiHuanLineWidth - neiYuanWidth*0.5;
CGFloat endAngle= M_PI * 2 * self.progress + (- M_PI_2);
UIBezierPath *neiYuanBezierPath = [UIBezierPath bezierPathWithArcCenter:centerPoint radius:neiYuanRadius startAngle:- M_PI_2 endAngle:endAngle clockwise:YES];
[self.huanColor set];
//设置线框
neiYuanBezierPath.lineWidth = neiYuanWidth;
//最后画出来
[neiYuanBezierPath stroke];
}
@end
四.由于时间关系做的不太精致,还有待改善,感兴趣的小伙伴们可以对其进行封装,使其更加的完善!

iOS中的下载管理器(支持断点续传)的更多相关文章
- IIS中发布FTP支持断点续传
IIS10中发布FTP默认就是支持断点续传的.
- iOS中消息传递方式
iOS中消息传递方式 在iOS中有很多种消息传递方式,这里先简单介绍一下各种消息传递方式. 1.通知:在iOS中由通知中心进行消息接收和消息广播,是一种一对多的消息传递方式. NSNotificati ...
- IOS中多版本,多设备类型支持注意事项
IOS系统从07年出来,到现在也有6年了,每年发布一次到两次新的设备,从iPhone1,iPhone2 ... iPhone4s再到最新的iPhone5.硬件在升级的过程中CPU的架构也可能发生变化, ...
- ios中safari无痕浏览模式下,localStorage的支持情况
前言 前阶段,测试提了个bug,在苹果手机中无痕模式下,搜索按钮不好使,无法跳页,同时搜索历史也没有展示(用户搜索历史时使用localStorage存储). 正文 iOS上Sarfari在无痕模式下, ...
- IOS中input键盘事件支持的解决方法
欢迎大家去我的网站详细查看http://genghongshuo.com.cn/ IOS中input键盘事件keyup.keydown.等支持不是很好, 用input监听键盘keyup事件,在安卓手机 ...
- ios设备中openGL所支持的最大纹理尺寸
这几天碰到一个在iphone4上显示图片未黑色矩形的bug,在其他机器上都正常 最后发现是图片打包尺寸的关系,iphone4无法读取2048以上大小的单个图片,所以其中的图片都显示成了黑色,希望对碰到 ...
- iOS中如何让TextView和TextField控件支持return键收起输入法
TextView和TextField控件是iOS中负责接收用户输入的控件,那当用户输入完成时怎么收起面板呢? 1.TextView和TextField控件获得焦点之后的第一反应就是弹出输入法面板: 2 ...
- ios 中不new Date 的格式 不支持年月日 以‘-’ 分割的格式
new Date("2018-1-5") 在 ios 中显示 invalid date - 换做 / 则可以顺利显示 new Date("2018/1/5")
- iOS中支付宝集成
iOS中支付宝集成 如今各种的App中都使用了三方支付的功能,现在将我在使用支付宝支付集成过程的心得分享一下,希望对大家都能有所帮助 要集成一个支付宝支付过程的环境,大致需要: 1>公司:先与支 ...
随机推荐
- 1203.3——循环语句 之 while
while循环 while循环的一般形式为: while(表达式){ 语句块 }其中表达式称为循环条件,语句块称为循环体. while语句的意思是:先计算表达式的值,当值为真 ...
- VC++ 控制台不自动退出
1.Ctrl+F5 2.结尾添加 getchar() 3.结尾添加 system("pause"); 参考:http://jingyan.baidu.com/article/555 ...
- UVA11806Cheerleaders(容斥)
转载请注明出处: http://www.cnblogs.com/fraud/ ——by fraud 题目意思:在m行n列的矩形网格中放k个相同的石子,问有多少中方法?每个格子最多放一 ...
- WordPress设置固定链接和邮件提醒遇到的问题
固定链接1.WordPress根目录下有一个.h...文件,记录文章链接类型对应的配置,要保证该文件的可写全权限:2.设置Apache2的rewrite模块启动,/etc/apache2/modle- ...
- H5本地存储
在HTML5中可以把数据长期存储在客户端,使用的对象就是localStorage. localStorage常用方法有setItem.getItem.removeItem.clear. 下面是一个存储 ...
- php word转HTML
因为安装的的xampp不知道如何查看我的Apache版本是多少,就先把com.allow_dcom=true打开了,但是仍旧报错说找不到com类,然后就把下面的extension扩展添加到php.in ...
- [XMPP]简易的聊天室实现[二](使用CocoaAsyncSocket)
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...
- 开心菜鸟系列学习笔记------javascript(4)
一.全局上下文中的变量对象: 1)全局对象(Global object) 是在进入任何执行上下文之前就已经创建了的对象:这个对象只存在一份,它的属性在程序中任何地方都可以访问,全局对象的 ...
- fragment点击跳转到外部Activity后,怎么通过返回按钮返回
楼主的情况应该是比较简单的吧,跟三楼说的一样,只要在D跳到下一个Activity的时候,D所在的Activity不要调用finish(),然后在下一个Activity关闭的时候直接调用finish() ...
- MotionEvent的getX(),getY()与getRawX(),getRawY()区别
getX()是表示Widget相对于自身左上角的x坐标,而getRawX()是表示相对于屏幕左上角的x坐标值(注意:这个屏幕左上角是手机屏幕左上角,不管activity是否有titleBar或是否全屏 ...