封装下载管理器实例教学
第一节:功能说明
首先,本篇文章教大家写一个最简单的下载管理器,不包含上传管理器。不过,上传管理器与下载管理器是一样的,后面会抛砖引玉,大家可以各自去尝试!
本篇文章所讲解的下载管理器具备以下功能:
开始下载某个视频
挂起某个视频下载(暂停下载)
恢复某个视频下载(继续下载)
可设置下载最大并发量
添加到下载队列
以下便是最基本的功能了,那么我们就根据这几个基本功能来实现。至于要做到后台自动下载及退出App,下次进入再自动恢复到上一次退出的状态的,这些不在本demo范围之内!
为了demo的简单,一切从简!
第二节:设计理念
设计理念通常都希望简单使用且易扩展易维护
与具体的下载类型无关,比如不管是视频下载还是音频下载又或是普通文件下载,都没有关系,都可通用
单个下载应保持功能的单一性,专心做一件事
第三节:如何设计整个下载管理器
考虑到需要记录进度及状态,所以一旦开启下载,整个app过程中都会存在,可考虑使用单例,也可以考虑非单例,但是非单例模式也得保证只创建一遍并交给appDelegate持有,其实与单例设计相当的。为了简化,这里采用的是单例设计。所以,下载管理器以单例形式存在。
考虑到需要处理并发下载问题,因此使用NSOperationQueue
考虑到下载类的功能单一性,采用子类化NSOperation
考虑到使用下载功能与文件类型无关,可定义协议,使model必须遵守,比如豆瓣开源的DOUAudioStreamer就是采用这种方式来实现
但是,为了demo的简单,这里没有定义协议,直接使用model了。大家可以在真正设计时,采用协议的式,以支持任意model。笔者在项目中真正去写的时候,也会采用协议的方式,支持下载、上传做任意类型的文件,包括视频、音频等。
本demo中,主要设计以下几个类:
HYBVideoOperation:子类化的NSOperation,用于专门做下载
HYBVideoModel:视频下载数据模型,包括视频下载地址、存储地址、进度、状态等,并持有HYBVideoOperation,以方便管理
HYBVideoManager:下载管理器,管理所有的HYBVideoModel
然后,我们还需要与UI交互,所以在cell中需要model。HYBVideoCell类为cell,强引用model!
那么,这整个交互是这样的:
HYBVideoManager —–》管理所有的HYBVideoModel
每个HYBVideoModel—–》持有一个HYBVideoOperation
HYBVideoOperation—-》弱持有一个HYBVideoModel
HYBVideoCell —–》持有一个HYBVideoModel,当进度或状态变化时,更新UI
所设计的回调全放在HYBVideoModel中,当HYBVideoModel的进度属性值和状态值发生变化时反馈到UI变化上!
第四节:子类化NSOperation
关于子类化NSOperation需要做哪些事件,最好还是先阅读笔者之前所写的一篇文章NSOperation/Queue,不过下面我也会列出一些要点:
重写isExecuting、isFinished、isConcurrent
重写cancel,并处理好isCancelled KVO处理
我们设计Operation时,采用NSURLSession实现下载,通过控制NSURLSessionDownloadTask,可实现下载、暂停下载和断点下载功能。
我们整个头文件的设计为:
@class HYBVideoModel;
@interface NSURLSessionTask (VideoModel)
// 为了更方便去获取,而不需要遍历,采用扩展的方式,可直接提取,提高效率
@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
这里还扩展了NSURLSessionTask,将模型与之关联,注意采用弱引用哦!我不知道这样设计是否合理,但是我个人认为这么设计的好处是:接口简单,与外部没有直接的联系,session来源于下载管理类,这样可统一管理。
当下载完成之后,一定要回调downloadFinished,目的是让任务退队。要让任务退队,只有保证isFinished为YES才能退队!
[self willChangeValueForKey:@“isFinished”];
[self willChangeValueForKey:@“isExecuting”];
_executing = NO;
_finished = YES;
[self didChangeValueForKey:@“isExecuting”];
[self didChangeValueForKey:@“isFinished”];
因为任务完成还可以重新下载,通常情况下不会自动退队。
第五节:反馈到UI展示进度及状态提示
我们通过模型来反馈到UI上,在进度和状态变化时,可以回调来更新UI。
首先,下载过程有很多种状态,我们定义成枚举:
typedef NS_ENUM(NSInteger, HYBVideoStatus) {
kHYBVideoStatusNone = 0, // 初始状态
kHYBVideoStatusRunning = 1, // 下载中
kHYBVideoStatusSuspended = 2, // 下载暂停
kHYBVideoStatusCompleted = 3, // 下载完成
kHYBVideoStatusFailed = 4, // 下载失败
kHYBVideoStatusWaiting = 5 // 等待下载
};
设计属性:
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;
// 用于断点下载记录,其实应该要存储到文件中,然后记录路径,但是为了简单,demo就不这么做了
@property (nonatomic, strong) NSData *resumeData;
// 下载后存储到此处
@property (nonatomic, copy) NSString *localPath;
@property (nonatomic, copy) NSString *progressText;
// 非常关键的属性,进度变化会自动回调onProgressChanged
@property (nonatomic, assign) CGFloat progress;
// 状态变化会自动回调onStatusChanged
@property (nonatomic, assign) HYBVideoStatus status;
// 这里为什么要引用operation且是强引用?因为管理器直接管理的是model,
// 而真正做下载任务的是operation。
// 为什么没有将这两个分别作为属性呢?为了整体更简单!
@property (nonatomic, strong) HYBVideoOperation *operation;
@property (nonatomic, copy) HYBVideoStatusChanged onStatusChanged;
@property (nonatomic, copy) HYBVideoProgressChanged onProgressChanged;
@property (nonatomic, readonly, copy) NSString *statusText;
@end
当然,不同的人来设计,可能会有不同的方式。我分析过好几种设计方式,但是列出来的好处,不如这一种。
当进度或者状态变化时,自动地回调:
- (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);
}
}
}
这样回调与下载管理类及下载类都没有直接的关系了,而model的回调直接反馈到UI层了!
非常好我支持^.^
(0) 0%
不好我反对
(0) 0%