基本思路:
-
判断本地文件,如果本地文件存在要判断文件的大小
-
如果没有本地文件,下载
-
如果本地文件存在,发送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;}
242

被折叠的 条评论
为什么被折叠?



