封裝下載管理器實例教學(xué)
第一節(jié):功能說明
首先,本篇文章教大家寫一個最簡單的下載管理器,不包含上傳管理器。不過,上傳管理器與下載管理器是一樣的,后面會拋磚引玉,大家可以各自去嘗試!
本篇文章所講解的下載管理器具備以下功能:
開始下載某個視頻
掛起某個視頻下載(暫停下載)
恢復(fù)某個視頻下載(繼續(xù)下載)
可設(shè)置下載最大并發(fā)量
添加到下載隊列
以下便是最基本的功能了,那么我們就根據(jù)這幾個基本功能來實現(xiàn)。至于要做到后臺自動下載及退出App,下次進(jìn)入再自動恢復(fù)到上一次退出的狀態(tài)的,這些不在本demo范圍之內(nèi)!
為了demo的簡單,一切從簡!
第二節(jié):設(shè)計理念
設(shè)計理念通常都希望簡單使用且易擴(kuò)展易維護(hù)
與具體的下載類型無關(guān),比如不管是視頻下載還是音頻下載又或是普通文件下載,都沒有關(guān)系,都可通用
單個下載應(yīng)保持功能的單一性,專心做一件事
第三節(jié):如何設(shè)計整個下載管理器
考慮到需要記錄進(jìn)度及狀態(tài),所以一旦開啟下載,整個app過程中都會存在,可考慮使用單例,也可以考慮非單例,但是非單例模式也得保證只創(chuàng)建一遍并交給appDelegate持有,其實與單例設(shè)計相當(dāng)?shù)摹榱撕喕@里采用的是單例設(shè)計。所以,下載管理器以單例形式存在。
考慮到需要處理并發(fā)下載問題,因此使用NSOperationQueue
考慮到下載類的功能單一性,采用子類化NSOperation
考慮到使用下載功能與文件類型無關(guān),可定義協(xié)議,使model必須遵守,比如豆瓣開源的DOUAudioStreamer就是采用這種方式來實現(xiàn)
但是,為了demo的簡單,這里沒有定義協(xié)議,直接使用model了。大家可以在真正設(shè)計時,采用協(xié)議的式,以支持任意model。筆者在項目中真正去寫的時候,也會采用協(xié)議的方式,支持下載、上傳做任意類型的文件,包括視頻、音頻等。
本demo中,主要設(shè)計以下幾個類:
HYBVideoOperation:子類化的NSOperation,用于專門做下載
HYBVideoModel:視頻下載數(shù)據(jù)模型,包括視頻下載地址、存儲地址、進(jìn)度、狀態(tài)等,并持有HYBVideoOperation,以方便管理
HYBVideoManager:下載管理器,管理所有的HYBVideoModel
然后,我們還需要與UI交互,所以在cell中需要model。HYBVideoCell類為cell,強(qiáng)引用model!
那么,這整個交互是這樣的:
HYBVideoManager —–》管理所有的HYBVideoModel
每個HYBVideoModel—–》持有一個HYBVideoOperation
HYBVideoOperation—-》弱持有一個HYBVideoModel
HYBVideoCell —–》持有一個HYBVideoModel,當(dāng)進(jìn)度或狀態(tài)變化時,更新UI
所設(shè)計的回調(diào)全放在HYBVideoModel中,當(dāng)HYBVideoModel的進(jìn)度屬性值和狀態(tài)值發(fā)生變化時反饋到UI變化上!
第四節(jié):子類化NSOperation
關(guān)于子類化NSOperation需要做哪些事件,最好還是先閱讀筆者之前所寫的一篇文章NSOperation/Queue,不過下面我也會列出一些要點(diǎn):
重寫isExecuting、isFinished、isConcurrent
重寫cancel,并處理好isCancelled KVO處理
我們設(shè)計Operation時,采用NSURLSession實現(xiàn)下載,通過控制NSURLSessionDownloadTask,可實現(xiàn)下載、暫停下載和斷點(diǎn)下載功能。
我們整個頭文件的設(shè)計為:
@class HYBVideoModel;
@interface NSURLSessionTask (VideoModel)
// 為了更方便去獲取,而不需要遍歷,采用擴(kuò)展的方式,可直接提取,提高效率
@property (nonatomic, weak) HYBVideoModel *hyb_videoModel;
@end
@interface HYBVideoOperation : NSOperation
- (instancetype)initWithModel:(HYBVideoModel *)model session:(NSURLSession *)session;
@property (nonatomic, weak) HYBVideoModel *model;
// 可以不公開此屬性
@property (nonatomic, strong, readonly) NSURLSessionDownloadTask *downloadTask;
- (void)suspend;
- (void)resume;
- (void)downloadFinished;
@end
這里還擴(kuò)展了NSURLSessionTask,將模型與之關(guān)聯(lián),注意采用弱引用哦!我不知道這樣設(shè)計是否合理,但是我個人認(rèn)為這么設(shè)計的好處是:接口簡單,與外部沒有直接的聯(lián)系,session來源于下載管理類,這樣可統(tǒng)一管理。
當(dāng)下載完成之后,一定要回調(diào)downloadFinished,目的是讓任務(wù)退隊。要讓任務(wù)退隊,只有保證isFinished為YES才能退隊!
[self willChangeValueForKey:@“isFinished”];
[self willChangeValueForKey:@“isExecuting”];
_executing = NO;
_finished = YES;
[self didChangeValueForKey:@“isExecuting”];
[self didChangeValueForKey:@“isFinished”];
因為任務(wù)完成還可以重新下載,通常情況下不會自動退隊。
第五節(jié):反饋到UI展示進(jìn)度及狀態(tài)提示
我們通過模型來反饋到UI上,在進(jìn)度和狀態(tài)變化時,可以回調(diào)來更新UI。
首先,下載過程有很多種狀態(tài),我們定義成枚舉:
typedef NS_ENUM(NSInteger, HYBVideoStatus) {
kHYBVideoStatusNone = 0, // 初始狀態(tài)
kHYBVideoStatusRunning = 1, // 下載中
kHYBVideoStatusSuspended = 2, // 下載暫停
kHYBVideoStatusCompleted = 3, // 下載完成
kHYBVideoStatusFailed = 4, // 下載失敗
kHYBVideoStatusWaiting = 5 // 等待下載
};
設(shè)計屬性:
typedef void(^HYBVideoStatusChanged)(HYBVideoModel *model);
typedef void(^HYBVideoProgressChanged)(HYBVideoModel *model);
@interface HYBVideoModel : NSObject
@property (nonatomic, copy) NSString *videoId;
@property (nonatomic, copy) NSString *videoUrl;
@property (nonatomic, copy) NSString *imageUrl;
@property (nonatomic, copy) NSString *title;
// 用于斷點(diǎn)下載記錄,其實應(yīng)該要存儲到文件中,然后記錄路徑,但是為了簡單,demo就不這么做了
@property (nonatomic, strong) NSData *resumeData;
// 下載后存儲到此處
@property (nonatomic, copy) NSString *localPath;
@property (nonatomic, copy) NSString *progressText;
// 非常關(guān)鍵的屬性,進(jìn)度變化會自動回調(diào)onProgressChanged
@property (nonatomic, assign) CGFloat progress;
// 狀態(tài)變化會自動回調(diào)onStatusChanged
@property (nonatomic, assign) HYBVideoStatus status;
// 這里為什么要引用operation且是強(qiáng)引用?因為管理器直接管理的是model,
// 而真正做下載任務(wù)的是operation。
// 為什么沒有將這兩個分別作為屬性呢?為了整體更簡單!
@property (nonatomic, strong) HYBVideoOperation *operation;
@property (nonatomic, copy) HYBVideoStatusChanged onStatusChanged;
@property (nonatomic, copy) HYBVideoProgressChanged onProgressChanged;
@property (nonatomic, readonly, copy) NSString *statusText;
@end
當(dāng)然,不同的人來設(shè)計,可能會有不同的方式。我分析過好幾種設(shè)計方式,但是列出來的好處,不如這一種。
當(dāng)進(jìn)度或者狀態(tài)變化時,自動地回調(diào):
- (void)setProgress:(CGFloat)progress {
if (_progress != progress) {
_progress = progress;
if (self.onProgressChanged) {
self.onProgressChanged(self);
} else {
NSLog(@“progress changed block is empty”);
}
}
}
- (void)setStatus:(HYBVideoStatus)status {
if (_status != status) {
_status = status;
if (self.onStatusChanged) {
self.onStatusChanged(self);
}
}
}
這樣回調(diào)與下載管理類及下載類都沒有直接的關(guān)系了,而model的回調(diào)直接反饋到UI層了!
非常好我支持^.^
(0) 0%
不好我反對
(0) 0%