效果演示

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