不少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是暴露出來最核心的模塊,在設計上主要考慮下邊幾件事情:
綜上所述,這基本上是寫任何一個框架的基本流程,在編碼以前先進行設計。 另外,在使用的過程當中,若是有任何問題,能夠給我留言,若是有新的需求,也能夠給我留言。
因爲水平有限,不免會出現錯誤,若是發現後,還望可以告知一聲。