示例
前言
很多iOS應用中都需要下載數據,并對這些下載的過程和結果進行管理,因此我才有了寫這個MCDownloader的想法。在IOS 文件下載器-MCDownloadManager這篇文章中,我使用GCD和集合來實現了這個功能,基本上也能滿足需求,這一部分的實現原理主要參考AFNetworking的源碼,有興趣的同學可以看看我寫的AFNetworking 3.0 源碼解讀系列。
但是本篇文章中講的MCDownloader的實現原理和上邊提到的不一樣,是基于NSOperation來實現的,可以說是我對SDWebImage源碼解讀的一些額外的擴展,同樣,有興趣的同學可以看看我寫的SDWebImage源碼解讀系列。
MCDownloader目前的版本是1.0.0,可以在這里下載https://github.com/agelessman/MCDownloader
功能
MCDownloader1.0.0版本提供了以下幾個功能:
- 多線程異步下載,支持自定義并發線程數。在上圖的示例圖中,采用的并發數為3
- 邊下載變保存,這一條是該下載器最重要的思想,數據被實時的保存在本地,同時支持斷點下載
- 十分方便的數據獲取能力,通過MCDownloadReceipt來對下載的數據進行抽象,幾乎所有的信息都能在MCDownloadReceipt中獲取
- 提供了下載進度,可以通過接口函數的block監聽下載進度和完成回調,也可以通過給MCDownloadReceipt綁定block來監聽block回調
- 支持顯示當前的下載速度
- 支持批量下載,批量取消功能
- 支持任務的暫停,取消,刪除功能
- 支持下載順序定制,先入先出或者后入先出
- 支持后臺和鎖屏下載
如何使用?
開啟下載
每一個下載任務的唯一標識是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回調都在主線程觸發。
暫?;蛉∠?/h4>
MCDownloader的暫停和取消功能是一樣的,由于內部下載是基于NSOperation實現的,因此每一個任務就是一個NSOperation,然后再把他們添加到隊列之中。當取消或者暫停一個任務后,在重新恢復下載,實際上會重新把該任務添加到隊列中,這一點一定要注意。
使用下邊的代碼來暫?;蛉∠粋€下載任務:
[[MCDownloader sharedDownloader] cancel:receipt completed:^{
[self.button setTitle:@"Start" forState:UIControlStateNormal];
}];
由于取消不是發生在主線程,所以需要一個completed來捕獲取消成功事件,然后在主線程調用。
移除數據
通過下邊的方法來移除保存在本地的數據:
[[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。通過它來對數據進行抽象封裝,我們先不管它是如何獲取的,只關心它需要暴露多少信息。這個類很簡單,我就不把代碼弄上來了,但是需要注意下邊幾點:
- 這些屬性被設計成只讀屬性,表明只在該類中獲取數據,不要修改其中的數據
- receipt需要保存在本地,我采用的是歸檔的方法進行持久化
- 文件名要做MD5處理
- 每一個receipt都綁定了一個狀態屬性
完成了模型的搭建后,就要處理最基本的下載任務了,MCDownloadOperation繼承自NSOperation,因此在MCDownloadOperation中我們就不需要關心線程的問題。我們在這個類中只做了下邊這幾件事:
- 開啟下載任務,在開啟任務的start方法中,我做了一些保證任務開始的必要措施,有興趣的可以去看看源碼
- 接受數據和寫入數據
- 處理下載過程中和完成后的回調函數
- 處理下載狀態
- 關心如何取消任務
接下來就到了最核心的地方,如何把MCDownloadReceipt和MCDownloadOperation組合在一起,也就是MCDownloader的內容。MCDownloader是暴露出來最核心的模塊,在設計上主要考慮下邊幾件事情:
- 需要一個單利對象來管理全局的情況
- 支持設置一些跟下載相關的額外信息,比如超時時間,請求頭和并發數等等
- 提供一些控制下載的常用方法,如何開始,取消,移除等等
綜上所述,這基本上是寫任何一個框架的基本流程,在編碼之前先進行設計。 另外,在使用的過程中,如果有任何問題,可以給我留言,如果有新的需求,也可以給我留言。
由于水平有限,難免會出現錯誤,如果發現后,還望能夠告知一聲。