iOS断点续传
2016-01-14 01:06
441 查看
基本思路:
判断本地文件,如果本地文件存在要判断文件的大小
如果没有本地文件,下载
如果本地文件存在,发送head请求获取服务器文件大小
本地文件大小 == 服务器文件大小,不下载
本地文件大小 < 服务器文件大小,从之前的位置开始下载
本地文件大小 > 服务器文件大小,删除本地文件,重新下载
检查服务器文件
获取服务器上的文件信息
检查本地文件
如果没有文件 返回0, 从头下载
如果文件大小 > 服务器文件大小 返回0 删除本地文件 从头下载
如果文件大小 == 服务器文件大小 返回文件大小 如果大小相等,不用下载
如果文件大小 < 服务器文件大小 返回文件大小 从之前的位置开始下载
下载
如果本地文件和服务器文件大小相等,不下载
从指定偏移处开始下载
range 取值
bytes=x-y 从x字节开始下载,下载到y字节
bytes=x- 从x字节开始下载,下载到最后
bytes=-x 从0字节开始下载,下载到x字节
异步下载
默认代理方法都在主线程上执行的,下载会卡死
异步下载
NSURLConnection 的代理方法,想在子线程上执行的话必须开启消息循环
把下载方法中的所有代码都放在异步队列中执行
在指定位置处开启消息循环,消息循环的模式必须是default模式
下载完成后的回调
下载完成后或出错之后要在主界面做提示,现在所有的下载操作都封装在Downloader这个类中,学习SDWebImage中的做法,给下载操作传入需要的block,当下载完成或出错的时候调用
修改Download二头文件中的下载方法,增加需要的block,进度,完成,出错的block
修改downloader.m中的实现方法,因为具体的进度,完成,出错都是在URLConnection的代理方法实现的,所以传入block后需要定义属性接收
定义block的属性
下载方法中给block属性赋值
对应的位置调用回调方法
URLConnection的下载方法中调用进度的回调,在当前子线程中执行
URLConnection的下载完成方法中调用完成的回调,下载完成会回归主线程调用,在主线程更新界面
URLConnection的下载出错的方法中调用出错的回调,在那个线程执行由调用者决定
如果文件已经存在,也要调用完成的方法
controller中调用
下载进度提示
界面上放置一个自定义按钮,设置大小
创建按钮的自定义类 (按钮必须是custom的 系统默认的模式button在重绘时会按钮会跟被点击一样,会不断闪烁)
定义一个progress的属性,把进度传过来
每当给progress属性赋值的时候调用setneeddisplay 重绘
重写drawRect方法,根据progress画圆
controller中显示进度
controller 中在下载进度的回调中调用
在storyboard中设置自定义类的属性
IB
暂停下载
暂停下载就是调用connection的cancel方法
代码重构
多点击屏幕几次,这个时候会不停的下载同一个文件
创建一个下载的单例的管理类
通过管理类缓存下载操作,解决重复下载同一个文件
下载管理类和缓存池
创建下载的管理类DownloaderManger单例
下载,调用下载器的下载方法
定义缓存池,当下载开始,把下载器缓存起来
下载之前先判断缓存池中是否有此下载操作
下载完成或失败后,从缓存池移除下载操作-----最终解决重复下载的问题
Downloader 改成NSOpration
改成NSOpration的好处:
可以设置最大并发数,限制下载文件的个数
可以设置依赖,让一个下载在另一个下载后面执行
可以暂停正在下载的任务
取消下载操作
下载操作还要取消正在执行的操作
在main方法的比较耗时的操作后面加上下面代码
判断本地文件,如果本地文件存在要判断文件的大小
如果没有本地文件,下载
如果本地文件存在,发送head请求获取服务器文件大小
本地文件大小 == 服务器文件大小,不下载
本地文件大小 < 服务器文件大小,从之前的位置开始下载
本地文件大小 > 服务器文件大小,删除本地文件,重新下载
检查服务器文件
获取服务器上的文件信息
//获取服务器上的文件大小和文件名
- (void)checkServerInfo:(NSURL *)url {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"HEAD";
NSURLResponse *response = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
self.expectedContentLength = response.expectedContentLength;
self.filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:response.suggestedFilename];
}
检查本地文件
如果没有文件 返回0, 从头下载
如果文件大小 > 服务器文件大小 返回0 删除本地文件 从头下载
如果文件大小 == 服务器文件大小 返回文件大小 如果大小相等,不用下载
如果文件大小 < 服务器文件大小 返回文件大小 从之前的位置开始下载
- (long long)checkLocalInfo {
NSFileManager *fileManger = [NSFileManager defaultManager];
//检查文件是否已存在
if (![fileManger fileExistsAtPath:self.filePath]) {
return 0;
}
//获取本地文件的大小
NSDictionary *fileAttrs = [fileManger attributesOfItemAtPath:self.filePath error:NULL];
long long fileSize = fileAttrs.fileSize;
if (fileSize > self.expectedContentLength) {
[fileManger removeItemAtPath:self.filePath error:NULL];
return 0;
}
return fileSize;
}
下载
如果本地文件和服务器文件大小相等,不下载
long long fileSize = [self checkLocalInfo];
if (fileSize == self.expectedContentLength) {
NSLog(@"文件已经下载");
return;
}
从指定偏移处开始下载
range 取值
bytes=x-y 从x字节开始下载,下载到y字节
bytes=x- 从x字节开始下载,下载到最后
bytes=-x 从0字节开始下载,下载到x字节
//从指定偏移处下载文件
- (void)downloadWithUrl:(NSURL *)url offset:(long long)offset {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
/*
range 取值
bytes=x-y 从x字节开始下载,下载到y字节
bytes=x- 从x字节开始下载,下载到最后
bytes=-x 从0字节开始下载,下载到x字节
*/
[request setValue:[NSString stringWithFormat:@"bytes=%lld",offset] forHTTPHeaderField:@"range"];
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
}
异步下载
默认代理方法都在主线程上执行的,下载会卡死
异步下载
NSURLConnection 的代理方法,想在子线程上执行的话必须开启消息循环
把下载方法中的所有代码都放在异步队列中执行
在指定位置处开启消息循环,消息循环的模式必须是default模式
- (void)downloadWithUrl:(NSURL *)url offset:(long long)offset {
[[NSOperationQueue new] addOperationWithBlock:^{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
/*
range 取值
bytes=x-y 从x字节开始下载,下载到y字节
bytes=x- 从x字节开始下载,下载到最后
bytes=-x 从0字节开始下载,下载到x字节
*/
[request setValue:[NSString stringWithFormat:@"bytes=%lld-",offset] forHTTPHeaderField:@"range"];
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
//启动消息循环
[[NSRunLoop currentRunLoop] run];
}];
}
下载完成后的回调
下载完成后或出错之后要在主界面做提示,现在所有的下载操作都封装在Downloader这个类中,学习SDWebImage中的做法,给下载操作传入需要的block,当下载完成或出错的时候调用
修改Download二头文件中的下载方法,增加需要的block,进度,完成,出错的block
修改downloader.m中的实现方法,因为具体的进度,完成,出错都是在URLConnection的代理方法实现的,所以传入block后需要定义属性接收
定义block的属性
@property (nonatomic, copy) void(^successBlock)(NSString *path);
@property (nonatomic, copy) void(^processBlock)(float process);
@property (nonatomic, copy) void(^errorBlock)(NSError *error);
下载方法中给block属性赋值
- (void)downloadWithUrlString:(NSString *)urlStr success:(void (^)(NSString *))successBlock process:(void (^)(float))processBlock error:(void (^)(NSError *))errorBlock {
self.successBlock = successBlock;
self.processBlock = processBlock;
self.errorBlock = errorBlock;
对应的位置调用回调方法
URLConnection的下载方法中调用进度的回调,在当前子线程中执行
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.stream write:data.bytes maxLength:data.length];
self.currentContentLength += data.length;
float process = self.currentContentLength * 1.0 / self.expectedContentLength;
if (self.processBlock) {
self.processBlock(process);
}
}
URLConnection的下载完成方法中调用完成的回调,下载完成会回归主线程调用,在主线程更新界面
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.stream close];
if (self.successBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
self.successBlock(self.filePath);
});
}
}
URLConnection的下载出错的方法中调用出错的回调,在那个线程执行由调用者决定
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.stream close];
if (self.errorBlock) {
self.errorBlock(error);
}
}
如果文件已经存在,也要调用完成的方法
if (fileSize == self.expectedContentLength) {
NSLog(@"文件已经下载");
if (self.successBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
self.successBlock(self.filePath);
});
}
return;
}
controller中调用
下载进度提示
界面上放置一个自定义按钮,设置大小
创建按钮的自定义类 (按钮必须是custom的 系统默认的模式button在重绘时会按钮会跟被点击一样,会不断闪烁)
定义一个progress的属性,把进度传过来
每当给progress属性赋值的时候调用setneeddisplay 重绘
//重写progress的set方法
- (void)setProgress:(float)progress {
_progress = progress;
[self setTitle:[NSString stringWithFormat:@"0.2f%%%",progress * 100] forState:UIControlStateNormal];
[self setNeedsDisplay];
}
重写drawRect方法,根据progress画圆
#define kLINEWIDTH 5
- (void)drawRect:(CGRect)rect {
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint center = CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5);
CGFloat radius = MIN(center.x, center.y) - kLINEWIDTH;
CGFloat startA = -M_PI_2;
CGFloat endA = startA + self.progress * 2 * M_PI;
[path addArcWithCenter:center radius:radius startAngle:startA endAngle:endA clockwise:YES];
path.lineCapStyle = kCGLineCapRound;
path.lineWidth = kLINEWIDTH;
[[UIColor orangeColor] setStroke];
[path stroke];
}
controller中显示进度
controller 中在下载进度的回调中调用
process:^(float process) {
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView.progress = process;
});
}
在storyboard中设置自定义类的属性
IB
暂停下载
暂停下载就是调用connection的cancel方法
- (void)pause {
[self.conn cancel];
}
代码重构
多点击屏幕几次,这个时候会不停的下载同一个文件
创建一个下载的单例的管理类
通过管理类缓存下载操作,解决重复下载同一个文件
下载管理类和缓存池
创建下载的管理类DownloaderManger单例
static id instance = nil;
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [super allocWithZone:zone];
});
return instance;
}
+ (instancetype)sharedDownloaderManger {
return [[self alloc] init];
}
下载,调用下载器的下载方法
定义缓存池,当下载开始,把下载器缓存起来
下载之前先判断缓存池中是否有此下载操作
下载完成或失败后,从缓存池移除下载操作-----最终解决重复下载的问题
- (void)pauseWithUrlString:(NSString *)urlStr {
SMHDownloader *downloader = self.dictCache[urlStr];
[downloader pause];
[self.dictCache removeObjectForKey:urlStr];
}
- (NSMutableDictionary *)dictCache {
if (_dictCache == nil) {
_dictCache = [NSMutableDictionary dictionaryWithCapacity:5];
}
return _dictCache;
}
- (void)downloadWithUrlString:(NSString *)urlStr success:(void (^)(NSString *))successBlock process:(void (^)(float))processBlock error:(void (^)(NSError *))errorBlock {
if (self.dictCache[urlStr]) {
NSLog(@"正在下载");
return;
}
SMHDownloader *downloader = [[SMHDownloader alloc] init];
[self.dictCache setObject:downloader forKey:urlStr];
[downloader downloadWithUrlString:urlStr success:^(NSString *path) {
[self.dictCache removeObjectForKey:urlStr];
if (successBlock) {
successBlock(path);
}
} process:processBlock error:^(NSError *error) {
[self.dictCache removeObjectForKey:urlStr];
if (errorBlock) {
errorBlock(error);
}
}];
}
Downloader 改成NSOpration
改成NSOpration的好处:
可以设置最大并发数,限制下载文件的个数
可以设置依赖,让一个下载在另一个下载后面执行
可以暂停正在下载的任务
- (void)main {
NSURL *url = [NSURL URLWithString:self.urlStr];
[self checkServerInfo:url];
long long fileSize = [self checkLocalInfo];
if (fileSize == self.expectedContentLength) {
NSLog(@"文件已经下载");
if (self.successBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
self.successBlock(self.filePath);
});
}
return;
}
self.currentContentLength = fileSize;
[self downloadWithUrl:url offset:fileSize];
}
+ (instancetype)downloaderWithUrlString:(NSString *)urlStr success:(void (^)(NSString *))successBlock process:(void (^)(float))processBlock error:(void (^)(NSError *))errorBlock {
SMHDownloader *downloader = [[self alloc] init];
downloader.successBlock = successBlock;
downloader.processBlock = processBlock;
downloader.errorBlock = errorBlock;
downloader.urlStr = urlStr;
return downloader;
}
取消下载操作
- (void)pauseWithUrlString:(NSString *)urlStr {
SMHDownloader *downloader = self.dictCache[urlStr];
if (downloader == nil) {
NSLog(@"没有此操作");
return;
}
[downloader pause];
[self.dictCache removeObjectForKey:urlStr];
}
下载操作还要取消正在执行的操作
在main方法的比较耗时的操作后面加上下面代码
//取消正在下载的操作
if (self.isCancelled) {
return;
}
相关文章推荐
- iOS9 tableVIewCell的分割线不显示,只有在滑动的时候才显示?
- 【iOS开发】类簇--抽象工厂模式在OC中的使用
- 解析iOS 9.3新功能:又多了1个选iPhone理由(转)
- IOS 开发之_多线程
- IOS开发之_文件规定与Plist文件读写
- IOS开发之_本地音乐,录音,打包测试程序,本地通知;
- IOS开发学习笔记
- 短信验证
- IOS开发之_短信发送与拨打电话
- IOS开发之_导航,传感器,摇一摇,蓝牙等
- IOS开发之_快速单例
- TableView滑动不加载
- TableView文字自适应高度
- 干货!总结19个提升iOS开发技术的必看教程!
- iOS之[文件下载 / 大文件下载 / 断点下载]
- IOS [__NSCFString containsString:]报错、闪退、崩溃
- 【iOS开发】原生XML解析
- ios-AutoLayout(自动布局代码控制)简单总结
- iOS 知识储备
- 重新打包第三方框架ZBarSDK静态库,同时支持真机与模拟器测试