不少iOS应用中都须要下载数据,并对这些下载的过程和结果进行管理,所以我才有了写这个MCDownloader的想法。在IOS 文件下载器-MCDownloadManager这篇文章中,我使用GCD和集合来实现了这个功能,基本上也能知足需求,这一部分的实现原理主要参考AFNetworking的源码,有兴趣的同窗能够看看我写的AFNetworking 3.0 源码解读系列。html
可是本篇文章中讲的MCDownloader的实现原理和上边提到的不同,是基于NSOperation来实现的,能够说是我对SDWebImage源码解读的一些额外的扩展,一样,有兴趣的同窗能够看看我写的SDWebImage源码解读系列。git
MCDownloader目前的版本是1.0.0,能够在这里下载https://github.com/agelessman/MCDownloadergithub
MCDownloader1.0.0版本提供了如下几个功能:缓存
每个下载任务的惟一标识是url,所以咱们使用下边的代码开始一个下载任务:网络
[[MCDownloader sharedDownloader] downloadDataWithURL:[NSURL URLWithString:url] progress:^(NSInteger receivedSize, NSInteger expectedSize, NSInteger speed, NSURL * _Nullable targetURL) { } completed:^(MCDownloadReceipt * _Nullable receipt, NSError * _Nullable error, BOOL finished) { NSLog(@"==%@", error.description); }];
能够在上边的progress和completed中自定义处理方法。进度和完成的block回调都在主线程触发。多线程
MCDownloader的暂停和取消功能是同样的,因为内部下载是基于NSOperation实现的,所以每个任务就是一个NSOperation,而后再把他们添加到队列之中。当取消或者暂停一个任务后,在从新恢复下载,实际上会从新把该任务添加到队列中,这一点必定要注意。并发
使用下边的代码来暂停或取消一个下载任务:框架
[[MCDownloader sharedDownloader] cancel:receipt completed:^{ [self.button setTitle:@"Start" forState:UIControlStateNormal]; }];
因为取消不是发生在主线程,因此须要一个completed来捕获取消成功事件,而后在主线程调用。less
经过下边的方法来移除保存在本地的数据:异步
[[MCDownloader sharedDownloader] remove:receipt completed:^{ [self.tableView reloadData]; }];
能够经过下边的代码来获取数据的一些信息,这些信息既能够在下载过程当中获取,也能够在下载完成后获取。
MCDownloadReceipt *receipt = [[MCDownloader sharedDownloader] downloadReceiptForURLString:self.url];
经过上边的代码能够看出来,url被当作数据的惟一标识。在上图的例子中,咱们是在cell中更新下载进度的,为了防止cell的复用问题,我为每一个receipt绑定了progress和complete回调block:
__weak typeof(receipt) weakReceipt = receipt; receipt.downloaderProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSInteger speed, NSURL * _Nullable targetURL) { __strong typeof(weakReceipt) strongReceipt = weakReceipt; if ([targetURL.absoluteString isEqualToString:self.url]) { [self.button setTitle:@"Stop" forState:UIControlStateNormal]; self.bytesLable.text = [NSString stringWithFormat:@"%0.1fm/%0.1fm", receivedSize/1024.0/1024,expectedSize/1024.0/1024]; self.progressView.progress = (receivedSize/1024.0/1024) / (expectedSize/1024.0/1024); self.speedLable.text = [NSString stringWithFormat:@"%@/s", strongReceipt.speed ?: @"0"]; } }; receipt.downloaderCompletedBlock = ^(MCDownloadReceipt *receipt, NSError * _Nullable error, BOOL finished) { if (error) { [self.button setTitle:@"Start" forState:UIControlStateNormal]; self.nameLabel.text = @"Download Failure"; }else { [self.button setTitle:@"Play" forState:UIControlStateNormal]; self.nameLabel.text = @"Download Finished"; } };
在某种场景下须要取消所有的下载,好比说监听到网络状态变成4G时,须要询问用户是否继续下载。又或者在须要清空缓存的时候:
[[MCDownloader sharedDownloader] cancelAllDownloads]; [[MCDownloader sharedDownloader] removeAndClearAll];
上边说的这些功能,在demo中都有演示。
因为下载功能不算是特别复杂的功能,因此我就简单的说说内部的实现原理。
在代码设计之初,我最早写的类就是MCDownloadReceipt。经过它来对数据进行抽象封装,咱们先无论它是如何获取的,只关心它须要暴露多少信息。这个类很简单,我就不把代码弄上来了,可是须要注意下边几点:
完成了模型的搭建后,就要处理最基本的下载任务了,MCDownloadOperation继承自NSOperation,所以在MCDownloadOperation中咱们就不须要关心线程的问题。咱们在这个类中只作了下边这几件事:
接下来就到了最核心的地方,如何把MCDownloadReceipt和MCDownloadOperation组合在一块儿,也就是MCDownloader的内容。MCDownloader是暴露出来最核心的模块,在设计上主要考虑下边几件事情:
综上所述,这基本上是写任何一个框架的基本流程,在编码以前先进行设计。 另外,在使用的过程当中,若是有任何问题,能够给我留言,若是有新的需求,也能够给我留言。
因为水平有限,不免会出现错误,若是发现后,还望可以告知一声。