效果演示
用法和demo:
https://github.com/xshenpan/iOSdemo.git
写它的原因
- 学习了网络这一块之后没做过什么东西,感觉不踏实,于是将单任务版的断点续传做了加强,变成了多任务断点续传。虽然还是BUG不断,但是至少感觉踏实些了。
- 以前学习过怎么用第三方框架,但是从没想过自己会写,于是想着既然要写个多任务的下载demo,为什么不把他封装一下呢?于是就开始去想怎么写,然后自己用“http file server”作为内网的服务器放了一些资源,然后慢慢写慢慢改看外界怎么使用方便,断断续续写写改改一个星期终于能够基本使用了。然后就将其分享出来,共同学习。
- 当然,代码有许多地方欠缺考虑,经不住暴力测试
- 作为一个初学者,代码中免不了许多BUG、考虑不周、思想错误、接口不合理等等问题。英语也比较烂,其中可能有各种句子不通,意义不对的位置。写这个小小的demo也是为了加强学习,希望大家能够对其中的各种错误轻喷
多任务下载怎么写
-
怎么开头呢?
- 既然是多任务,那就是单任务的组合,先把单任务写了。用的就是MJ老师视频里讲的单任务下载的方法,不带断点续传的单任务
-
单任务写好了然后呢?
- 多任务是对单任务下载的管理,那么单任务应该封装的简单易用。任务应该能开始/暂停/关闭,能读取下载进度,获得文件大小,能知道文件是否下载完成,是否下载出错。于是封装成了一个"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;
- 单任务封装好了,多任务要怎样做?
- 首先是多个任务怎么保存起来?
- 我用一个数组将所有的任务对象保存起来,像对列一样使用数组,就能实现先加进来的任务先开始任务,后加进来的任务后等待前面的任务完成,由于数组又具有随机访问能力,所以可以对任何地方的任务进行控制
- 任务由谁管理呢?
- 当然任务的管理不可能交给控制器,那样失去了封装的意义了。所以应该由一个统一的对象管理。一般来说,像浏览器还有一些下载软件,他们添加任务和管理任务不在同一个控制器,可能在多个地方添加任务,在同一个控制器管理任务。那么管理类应该做成单例模式,这样就能在不同的地方拿到统一管理对象进行添加任务,也能在管理任务的控制器中统一管理任务了
- 所以将管理对象做成了一个单例模式的类 "XBDownloadManager",外界要方便对多任务进行管理,那么怎么去告诉管理器我需要控制哪个任务呢? 由于使用的是数组存储的任务对象,那么则可以很方便的使用索引操作任务对象。万一不想使用索引管理对象怎么办,所以在使用数组存储对象的时候同时也使用了字典对任务进行存储,这样既可以使用索引对任务进行控制(使用Tableview管理任务是,索引是比较方便的),也可以使用key对任务进行控制。我要获得任务详细信息怎么办? 代理的作用就体现出来了,使用一些方法去通知代理某个任务的就具体状态。 但是我不想知道任务具体信息怎么办?我只是添加一下任务,在多个控制器的右上角或什么地方显示一些任务的数量,这是一个一对多的问题,那么通知可以很好的解决这一问题。最终的接口如下:
- 首先是多个任务怎么保存起来?
static const CGFloat kManagerProgressUpdateInterval = 0.5; //unit : second
static NSString * const kXBDownloadManagerNotification = @"XBDownloadManagerNotification";
static NSString * const kManagerNotificationTaskNumberKey = @"XBDownloadManagerTaskNumber";
@protocol XBDownloadManagerDelegate <NSObject>
@optional
//删除任务时调用,内部保证同一任务的其他代理方法会在该方法之前调用
- (void)managerDeleteTaskForKey:(NSString *)key atIndex:(NSInteger)idx;
//获得响应文件长度是调用
- (void)managerTaskFileLength:(NSInteger)fileLength forKey:(NSString *)key atIndex:(NSInteger)idx;
//进度和速度更新是调用,由kManagerProgressUpdateInterval控制
- (void)managerRefreshTaskProgress:(CGFloat)progress speed:(CGFloat)speed forKey:(NSString *)key atIndex:(NSInteger)idx;
//任务状态改变时调用
- (void)managerTaskStatusChanged:(XBDownloadTaskStatus)status forKey:(NSString *)key atIndex:(NSInteger)idx;
//任务完成时调用
- (void)managerTaskCompleteWithError:(NSError *)error forKey:(NSString *)key atIndex:(NSInteger)idx;
//添加任务,或设置代理时调用
- (void)managerAddTaskName:(NSString *)name andStatus:(XBDownloadTaskStatus)status fileLength:(NSInteger)length forKey:(NSString *)key atIndex:(NSInteger)idx;
@end
@interface XBDownloadManager : NSObject
/** 最大同时下载任务数,最大只能有10个 */
@property (nonatomic, assign) NSInteger maxDownloadTask;
/** 代理 */
@property (nonatomic, weak, readonly) id<XBDownloadManagerDelegate> delegate;
/** 任务数量 */
@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;
//查询任务
- (XBDownloadTaskInfo *)taskInfoWithIndex:(NSInteger)idx;
- (XBDownloadTaskInfo *)taskInfoWithKey:(NSString *)key;
//设置代理和代理执行的队列
- (void)setDelegate:(id<XBDownloadManagerDelegate>)delegate andDelegateQueue:(NSOperationQueue *)queue;
/** 添加一个下载任务,path=nil这默认在cache/xbdownload目录 路经以home开始, key=nil 则key = url.md5string */
- (NSString *)addDownloadTaskWithUrl:(NSString *)url andRelativePath:(NSString *)path taskKey:(NSString *)key taskExist:(void(^)(NSString *key))exist;
+ (instancetype)manager;
管理类怎么实现?
-
断点续传怎么做?
- 先前实现了单任务下载,但是那个单任务是不带断点续传的。简单的断点续传就是在请求头中设置一个 "Range:"字段,则服务器就会从你指定的地方传输数据。所以将不能断点续传单任务下载变成能够断点续传的单任务下载只需要改变请求头即可。
- 所以下载的任务是改变传递给 "XBDownloadTask"类的请求头即可实现单任务下载。
-
请求头怎么设置呢?
- 假设现在有一个任务正在下载中,程序突然被中断了,由于"XBDownloadTask"使用的OutputStream类将数据写入文件,则下载了多少数据文件就是多大。那么再次启动该任务时,读取该任务对应的文件获取其大小生成一个带有"Range:bytes=xxx-"的请求头即可从上次中断的地方继续下载
-
请求头好设置,那么我怎么知道从什么地方下载?我下载的文件名叫什么?
- 因为程序被中段后,不可能叫用户再次去点击一下下载链接,所以我们不可能在程序中断之后再次获得链接,所有我们要在任务一添加进来的时候讲链接存储到磁盘中,当然要记录的不仅仅是一个url,还有用户指定的目录等等,此时可以在创建一个类去记录任务的下载状态信息,该类遵守NSCoding协议,将所有的任务信息记录在一个文件中,该类在里面叫做 "XBDownloadInfo"
-
信息记录好之后,怎么对加入的任务进行控制,调度?
- 因为要限制最大同时进行的任务数,所以用一个
maxDownloadTask
记录最大的任务数,使用currentTask
记录当前正在执行的任务数,通过比较当前任务数与最大任务数决定是否启动下一个任务,如果需要启动任务,那么从队尾开始向前遍历,找到一个处于等待状态的任务启动它。每当一个任务执行完都会尝试去启动一个等待任务,这样任务就能循环不断的执行下了。
- 因为要限制最大同时进行的任务数,所以用一个
//三个主要的调度方法
//尝试启动指定的任务
- (void)tryStartupTheTask:(XBDownloadTaskRecord *)taskRecord;
//尝试启动等待的任务
- (NSInteger)tryStartupWaitingTask;
//根据任务记录,创建下载任务
- (void)startupTaskWithRecord:(XBDownloadTaskRecord *)taskRecord;
最后
- 我也不知道乱七八糟说了一堆把大概说清楚没有,表述的乱了点。
- 还是作为新手还是希望能对其他新手有点帮助吧!
- 修复了视频中第二次进入下载任务管理界面时文件大小不显示的BUG
- 继续去学习新的知识了,暂且把它的bug放一放吧。
补充一点
- 还有一种风格的下载界面的demo没写,就是要添加的任务和正在下载的任务在同一个界面,估计要等一久在写了