效果演示
demo6.gif
用法和demo:
https://github.com/xshenpan/iOSdemo.git
寫它的原因
- 學(xué)習(xí)了網(wǎng)絡(luò)這一塊之后沒做過什么東西,感覺不踏實,于是將單任務(wù)版的斷點續(xù)傳做了加強,變成了多任務(wù)斷點續(xù)傳。雖然還是BUG不斷,但是至少感覺踏實些了。
- 以前學(xué)習(xí)過怎么用第三方框架,但是從沒想過自己會寫,于是想著既然要寫個多任務(wù)的下載demo,為什么不把他封裝一下呢?于是就開始去想怎么寫,然后自己用“http file server”作為內(nèi)網(wǎng)的服務(wù)器放了一些資源,然后慢慢寫慢慢改看外界怎么使用方便,斷斷續(xù)續(xù)寫寫改改一個星期終于能夠基本使用了。然后就將其分享出來,共同學(xué)習(xí)。
- 當(dāng)然,代碼有許多地方欠缺考慮,經(jīng)不住暴力測試
- 作為一個初學(xué)者,代碼中免不了許多BUG、考慮不周、思想錯誤、接口不合理等等問題。英語也比較爛,其中可能有各種句子不通,意義不對的位置。寫這個小小的demo也是為了加強學(xué)習(xí),希望大家能夠?qū)ζ渲械母鞣N錯誤輕噴
多任務(wù)下載怎么寫
-
怎么開頭呢?
- 既然是多任務(wù),那就是單任務(wù)的組合,先把單任務(wù)寫了。用的就是MJ老師視頻里講的單任務(wù)下載的方法,不帶斷點續(xù)傳的單任務(wù)
-
單任務(wù)寫好了然后呢?
- 多任務(wù)是對單任務(wù)下載的管理,那么單任務(wù)應(yīng)該封裝的簡單易用。任務(wù)應(yīng)該能開始/暫停/關(guān)閉,能讀取下載進度,獲得文件大小,能知道文件是否下載完成,是否下載出錯。于是封裝成了一個"XBDownloadTask"類,并提供了下面的額接口
typedef void(^XBCompleteBlock)(NSString *filePath, NSError *error);
typedef void(^XBDownloadProgressBlock)(NSInteger bytesRead, NSInteger totalBytesRead , NSInteger totalBytesExpectedToRead);
- (void)pause;
- (void)start;
- (void)cancel;
- (void)setProgressBlock:(XBDownloadProgressBlock)progressBlock;
- (void)setCompleteBlock:(XBCompleteBlock)completeBlock;
- (instancetype)downloadWithRequest:(NSURLRequest *)req andTempFileName:(NSString *)name;
- 單任務(wù)封裝好了,多任務(wù)要怎樣做?
- 首先是多個任務(wù)怎么保存起來?
- 我用一個數(shù)組將所有的任務(wù)對象保存起來,像對列一樣使用數(shù)組,就能實現(xiàn)先加進來的任務(wù)先開始任務(wù),后加進來的任務(wù)后等待前面的任務(wù)完成,由于數(shù)組又具有隨機訪問能力,所以可以對任何地方的任務(wù)進行控制
- 任務(wù)由誰管理呢?
- 當(dāng)然任務(wù)的管理不可能交給控制器,那樣失去了封裝的意義了。所以應(yīng)該由一個統(tǒng)一的對象管理。一般來說,像瀏覽器還有一些下載軟件,他們添加任務(wù)和管理任務(wù)不在同一個控制器,可能在多個地方添加任務(wù),在同一個控制器管理任務(wù)。那么管理類應(yīng)該做成單例模式,這樣就能在不同的地方拿到統(tǒng)一管理對象進行添加任務(wù),也能在管理任務(wù)的控制器中統(tǒng)一管理任務(wù)了
- 所以將管理對象做成了一個單例模式的類 "XBDownloadManager",外界要方便對多任務(wù)進行管理,那么怎么去告訴管理器我需要控制哪個任務(wù)呢? 由于使用的是數(shù)組存儲的任務(wù)對象,那么則可以很方便的使用索引操作任務(wù)對象。萬一不想使用索引管理對象怎么辦,所以在使用數(shù)組存儲對象的時候同時也使用了字典對任務(wù)進行存儲,這樣既可以使用索引對任務(wù)進行控制(使用Tableview管理任務(wù)是,索引是比較方便的),也可以使用key對任務(wù)進行控制。我要獲得任務(wù)詳細(xì)信息怎么辦? 代理的作用就體現(xiàn)出來了,使用一些方法去通知代理某個任務(wù)的就具體狀態(tài)。 但是我不想知道任務(wù)具體信息怎么辦?我只是添加一下任務(wù),在多個控制器的右上角或什么地方顯示一些任務(wù)的數(shù)量,這是一個一對多的問題,那么通知可以很好的解決這一問題。最終的接口如下:
- 首先是多個任務(wù)怎么保存起來?
static const CGFloat kManagerProgressUpdateInterval = 0.5; //unit : second
static NSString * const kXBDownloadManagerNotification = @"XBDownloadManagerNotification";
static NSString * const kManagerNotificationTaskNumberKey = @"XBDownloadManagerTaskNumber";
@protocol XBDownloadManagerDelegate <NSObject>
@optional
//刪除任務(wù)時調(diào)用,內(nèi)部保證同一任務(wù)的其他代理方法會在該方法之前調(diào)用
- (void)managerDeleteTaskForKey:(NSString *)key atIndex:(NSInteger)idx;
//獲得響應(yīng)文件長度是調(diào)用
- (void)managerTaskFileLength:(NSInteger)fileLength forKey:(NSString *)key atIndex:(NSInteger)idx;
//進度和速度更新是調(diào)用,由kManagerProgressUpdateInterval控制
- (void)managerRefreshTaskProgress:(CGFloat)progress speed:(CGFloat)speed forKey:(NSString *)key atIndex:(NSInteger)idx;
//任務(wù)狀態(tài)改變時調(diào)用
- (void)managerTaskStatusChanged:(XBDownloadTaskStatus)status forKey:(NSString *)key atIndex:(NSInteger)idx;
//任務(wù)完成時調(diào)用
- (void)managerTaskCompleteWithError:(NSError *)error forKey:(NSString *)key atIndex:(NSInteger)idx;
//添加任務(wù),或設(shè)置代理時調(diào)用
- (void)managerAddTaskName:(NSString *)name andStatus:(XBDownloadTaskStatus)status fileLength:(NSInteger)length forKey:(NSString *)key atIndex:(NSInteger)idx;
@end
@interface XBDownloadManager : NSObject
/** 最大同時下載任務(wù)數(shù),最大只能有10個 */
@property (nonatomic, assign) NSInteger maxDownloadTask;
/** 代理 */
@property (nonatomic, weak, readonly) id<XBDownloadManagerDelegate> delegate;
/** 任務(wù)數(shù)量 */
@property (nonatomic, assign, readonly) NSInteger taskNumber;
//控制方法
- (void)startAllDownloadTask;
- (void)pauseAllDownloadTask;
- (void)startWithIndex:(NSInteger)idx;
- (void)pauseWithIndex:(NSInteger)idx;
- (void)cancelWithIndex:(NSInteger)idx;
- (void)startWithKey:(NSString *)key;
- (void)pauseWithKey:(NSString *)key;
- (void)cancelWithKey:(NSString *)key;
- (void)reloadWithKey:(NSString *)key;
//查詢?nèi)蝿?wù)
- (XBDownloadTaskInfo *)taskInfoWithIndex:(NSInteger)idx;
- (XBDownloadTaskInfo *)taskInfoWithKey:(NSString *)key;
//設(shè)置代理和代理執(zhí)行的隊列
- (void)setDelegate:(id<XBDownloadManagerDelegate>)delegate andDelegateQueue:(NSOperationQueue *)queue;
/** 添加一個下載任務(wù),path=nil這默認(rèn)在cache/xbdownload目錄 路經(jīng)以home開始, key=nil 則key = url.md5string */
- (NSString *)addDownloadTaskWithUrl:(NSString *)url andRelativePath:(NSString *)path taskKey:(NSString *)key taskExist:(void(^)(NSString *key))exist;
+ (instancetype)manager;
管理類怎么實現(xiàn)?
-
斷點續(xù)傳怎么做?
- 先前實現(xiàn)了單任務(wù)下載,但是那個單任務(wù)是不帶斷點續(xù)傳的。簡單的斷點續(xù)傳就是在請求頭中設(shè)置一個 "Range:"字段,則服務(wù)器就會從你指定的地方傳輸數(shù)據(jù)。所以將不能斷點續(xù)傳單任務(wù)下載變成能夠斷點續(xù)傳的單任務(wù)下載只需要改變請求頭即可。
- 所以下載的任務(wù)是改變傳遞給 "XBDownloadTask"類的請求頭即可實現(xiàn)單任務(wù)下載。
-
請求頭怎么設(shè)置呢?
- 假設(shè)現(xiàn)在有一個任務(wù)正在下載中,程序突然被中斷了,由于"XBDownloadTask"使用的OutputStream類將數(shù)據(jù)寫入文件,則下載了多少數(shù)據(jù)文件就是多大。那么再次啟動該任務(wù)時,讀取該任務(wù)對應(yīng)的文件獲取其大小生成一個帶有"Range:bytes=xxx-"的請求頭即可從上次中斷的地方繼續(xù)下載
-
請求頭好設(shè)置,那么我怎么知道從什么地方下載?我下載的文件名叫什么?
- 因為程序被中段后,不可能叫用戶再次去點擊一下下載鏈接,所以我們不可能在程序中斷之后再次獲得鏈接,所有我們要在任務(wù)一添加進來的時候講鏈接存儲到磁盤中,當(dāng)然要記錄的不僅僅是一個url,還有用戶指定的目錄等等,此時可以在創(chuàng)建一個類去記錄任務(wù)的下載狀態(tài)信息,該類遵守NSCoding協(xié)議,將所有的任務(wù)信息記錄在一個文件中,該類在里面叫做 "XBDownloadInfo"
-
信息記錄好之后,怎么對加入的任務(wù)進行控制,調(diào)度?
- 因為要限制最大同時進行的任務(wù)數(shù),所以用一個
maxDownloadTask
記錄最大的任務(wù)數(shù),使用currentTask
記錄當(dāng)前正在執(zhí)行的任務(wù)數(shù),通過比較當(dāng)前任務(wù)數(shù)與最大任務(wù)數(shù)決定是否啟動下一個任務(wù),如果需要啟動任務(wù),那么從隊尾開始向前遍歷,找到一個處于等待狀態(tài)的任務(wù)啟動它。每當(dāng)一個任務(wù)執(zhí)行完都會嘗試去啟動一個等待任務(wù),這樣任務(wù)就能循環(huán)不斷的執(zhí)行下了。
- 因為要限制最大同時進行的任務(wù)數(shù),所以用一個
//三個主要的調(diào)度方法
//嘗試啟動指定的任務(wù)
- (void)tryStartupTheTask:(XBDownloadTaskRecord *)taskRecord;
//嘗試啟動等待的任務(wù)
- (NSInteger)tryStartupWaitingTask;
//根據(jù)任務(wù)記錄,創(chuàng)建下載任務(wù)
- (void)startupTaskWithRecord:(XBDownloadTaskRecord *)taskRecord;
最后
- 我也不知道亂七八糟說了一堆把大概說清楚沒有,表述的亂了點。
- 還是作為新手還是希望能對其他新手有點幫助吧!
- 修復(fù)了視頻中第二次進入下載任務(wù)管理界面時文件大小不顯示的BUG
- 繼續(xù)去學(xué)習(xí)新的知識了,暫且把它的bug放一放吧。
補充一點
- 還有一種風(fēng)格的下載界面的demo沒寫,就是要添加的任務(wù)和正在下載的任務(wù)在同一個界面,估計要等一久在寫了