iOS多任務下載的簡要概述
多任務下載顧名思義就是多個任務同時下載,各個任務在同一時間一起下載,比如迅雷等下載軟件就具備這些功能,而iOS開發中也涉及到了一些多任務下載。本文就是一個多任務下載的簡要概述,如有錯誤請見諒。
- 獲取網絡數據
- 網絡數據轉模型
- 自定義cell
- 設置下載按鈕
- 單例管理下載
- 錯誤的進度回調演示
- 進度回調的保存提取和執行
- 進度展示
- 下載完成的回調
- 判斷是否正在下載
- 暫停下載
- 繼續下載
導圖
獲取網絡數據
- 獲取電子書列表數據的主方法
- (void)loadBookList
{
// URL
NSURL *URL = [NSURL URLWithString:@"http://42.62.15.101/yyting/bookclient/ClientGetBookResource.action?bookId=30776&imei=OEVGRDQ1ODktRUREMi00OTU4LUE3MTctOUFGMjE4Q0JDMTUy&nwt=1&pageNum=1&pageSize=50&q=114?=acca7b0f8bcc9603c25a52f572f3d863&sortType=0&token=KMSKLNNITOFYtR4iQHIE2cE95w48yMvrQb63ToXOFc8%2A"];
// session發起和啟動任務
[[[NSURLSession sharedSession] dataTaskWithURL:URL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 處理響應
if (error == nil && data != nil) {
// 反序列化
id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@ %@",[result class],result);
} else {
NSLog(@"%@",error);
}
}] resume];
}
網絡數據轉模型
- JSON數據示例
{
"buy": 0,
"downPrice": 0.0,
"feeType": 0,
"hasLyric": 0,
"id": 301500958,
"length": 0,
"listenPrice": 0.0,
"name": "第001集_回到古代當獸醫",
"path": "http:\/\/kting.info:81\/asdb\/fiction\/chuanyue\/hdgddsy\/r4jigc2a.mp3",
"payType": 0,
"section": 1,
"size": 9913859
}
- 模型類.h文件
@interface BookModel : NSObject
/// 書名
@property (nonatomic,copy) NSString *name;
/// 音頻下載地址
@property (nonatomic,copy) NSString *path;
+ (instancetype)bookWithDict:(NSDictionary *)dict;
@end
- 模型類.m文件
+ (instancetype)bookWithDict:(NSDictionary *)dict
{
BookModel *book = [[BookModel alloc] init];
[book setValuesForKeysWithDictionary:dict];
return book;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{}
- (NSString *)description
{
return [NSString stringWithFormat:@"%@ -- %@",self.name,self.path];
}
- 獲取數據成功之后,實現網絡數據轉模型
- (void)loadBookList
{
// URL
NSURL *URL = [NSURL URLWithString:@"http://42.62.15.101/yyting/bookclient/ClientGetBookResource.action?bookId=30776&imei=OEVGRDQ1ODktRUREMi00OTU4LUE3MTctOUFGMjE4Q0JDMTUy&nwt=1&pageNum=1&pageSize=50&q=114?=acca7b0f8bcc9603c25a52f572f3d863&sortType=0&token=KMSKLNNITOFYtR4iQHIE2cE95w48yMvrQb63ToXOFc8%2A"];
// session發起和啟動任務
[[[NSURLSession sharedSession] dataTaskWithURL:URL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 處理響應
if (error == nil && data != nil) {
// 反序列化
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
// 取出list字段對應的字典數組
NSArray *list = result[@"list"];
// 定義臨時的可變數組
NSMutableArray *tmpM = [NSMutableArray arrayWithCapacity:list.count];
// 遍歷字典數組,取字典,轉模型
[list enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// 字典轉模型
BookModel *book = [BookModel bookWithDict:obj];
// 模型添加到臨時數組
[tmpM addObject:book];
}];
// 查看結果
NSLog(@"%@",tmpM);
} else {
NSLog(@"%@",error);
}
}] resume];
}
自定義cell
- 自定義cell類.h文件
#import <UIKit/UIKit.h>
#import "BookModel.h"
@interface BookCell : UITableViewCell
/// 接收VC傳入的模型
@property (nonatomic,strong) BookModel *book;
@end
- 自定義cell類.m文件
#import "BookCell.h"
@interface BookCell ()
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@end
@implementation BookCell
- (void)awakeFromNib {
// Initialization code
}
- (void)setBook:(BookModel *)book
{
_book = book;
self.nameLabel.text = book.name;
}
@end
- 控制器里面拿到數據源數組之后,刷新列表
- 注意 : NSURLSession的回調默認是在子線程異步執行的
- 所以 : 拿到數據源數組之后的刷新列表需要手動的回到主線程
- (void)loadBookList
{
// URL
NSURL *URL = [NSURL URLWithString:@"http://42.62.15.101/yyting/bookclient/ClientGetBookResource.action?bookId=30776&imei=OEVGRDQ1ODktRUREMi00OTU4LUE3MTctOUFGMjE4Q0JDMTUy&nwt=1&pageNum=1&pageSize=50&q=114?=acca7b0f8bcc9603c25a52f572f3d863&sortType=0&token=KMSKLNNITOFYtR4iQHIE2cE95w48yMvrQb63ToXOFc8%2A"];
// session發起和啟動任務
[[[NSURLSession sharedSession] dataTaskWithURL:URL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 處理響應
if (error == nil && data != nil) {
// 反序列化
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
// 取出list字段對應的字典數組
NSArray *list = result[@"list"];
// 定義臨時的可變數組
NSMutableArray *tmpM = [NSMutableArray arrayWithCapacity:list.count];
// 遍歷字典數組,取字典,轉模型
[list enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// 字典轉模型
BookModel *book = [BookModel bookWithDict:obj];
// 模型添加到臨時數組
[tmpM addObject:book];
}];
// 給數據源數組賦值
_bookList = tmpM.copy;
// 刷新列表
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.tableView reloadData];
}];
} else {
NSLog(@"%@",error);
}
}] resume];
}
- UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _bookList.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
BookCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BookCell" forIndexPath:indexPath];
// 獲取cell對應的模型
BookModel *book = _bookList[indexPath.row];
// 給cell傳遞模型數據
cell.book = book;
return cell;
}
設置下載按鈕
- 把cell系統的accessoryView設置成下載按鈕
- (void)awakeFromNib {
// 創建右側下載按鈕
UIButton *downloadBtn = [[UIButton alloc] init];
[downloadBtn setTitle:@"下載" forState:UIControlStateNormal];
[downloadBtn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[downloadBtn setTitle:@"暫停" forState:UIControlStateSelected];
[downloadBtn sizeToFit];
// 把cell系統的accessoryView設置成按鈕
self.accessoryView = downloadBtn;
// 添加下載按鈕監聽事件
[downloadBtn addTarget:self action:@selector(downloadBtnClick:) forControlEvents:UIControlEventTouchUpInside];
}
-
下載按鈕點擊事件
- 改變按鈕的狀態,顯示正確的文字
- 發送代理消息,讓控制器去下載音頻文件
-
改變按鈕的狀態,顯示正確的文字
開發技巧提醒 : 在cell上添加按鈕時,如何解決按鈕狀態因為cell的復用而復用的問題
方案一 : 自定義按鈕,暴露一個按鈕保存選中狀態的BOOL屬性` `方案二 : 在模型里面,定義一個按鈕保存選中狀態的BOOL屬性`
此處選擇方案二 : 模型類增加記錄按鈕選中狀態的屬性
/// 記錄按鈕的選中狀態
@property (nonatomic,assign) BOOL isSelected;
按鈕點擊事件
- (void)downloadBtnClick:(UIButton *)btn
{
// 1.改變按鈕選中時的文字信息
// 這種改變按鈕狀態的方式,在cell復用時,狀態也會復用
// btn.selected = !btn.isSelected;
// 1.1 使用模型記錄按鈕選中狀態,避免cell復用時出問題
self.book.isSelected = !self.book.isSelected;
// 1.2 根據記錄的狀態設置title
NSString *title = (self.book.isSelected == YES) ? @"暫停" : @"下載";
[btn setTitle:title forState:UIControlStateNormal];
// 2.發送代理消息,讓控制器去下載音頻文件
if ([self.delegate respondsToSelector:@selector(downloadBtnClick:)]) {
[self.delegate downloadBtnClick:self];
}
}
模型的setter里面解決按鈕復用的問題
- (void)setBook:(BookModel *)book
{
_book = book;
// 提示 : 解決cell滾動時,按鈕狀態復用的問題
// 取出按鈕,根據之前點擊時記錄的狀態,設置按鈕的文字
UIButton *btn = (UIButton *)self.accessoryView;
NSString *title = (self.book.isSelected == YES) ? @"暫停" : @"下載";
[btn setTitle:title forState:UIControlStateNormal];
self.nameLabel.text = book.name;
}
- 發送代理消息,通知控制器去下載音頻文件
引入代理
@interface ViewController () <UITableViewDataSource,BookCellDelegate>
@end
遵守代理
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
BookCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BookCell" forIndexPath:indexPath];
// 遵守代理
cell.delegate = self;
// 獲取cell對應的模型
BookModel *book = _bookList[indexPath.row];
// 給cell傳遞模型數據
cell.book = book;
return cell;
}
控制器實現代理方法
- (void)downloadBtnClick:(BookCell *)cell
{
// 獲取點擊的是第幾行cell
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
NSLog(@"你該去下載了 %zd",indexPath.row);
}
單例管理下載
單例直接管理NSURLSession去實現下載功能
- 單例類.h文件
@interface DownloadManager : NSObject
+ (instancetype)sharedManager;
/**
* 單例下載的主方法
*
* @param URLString 下載地址
* @param progressBlock 下載進度回調
* @param completionBlock 下載完成回調
*/
- (void)downloadWithURLString:(NSString *)URLString progress:(void(^)(float progress))progressBlock completion:(void(^)(NSString *filePath))completionBlock;
@end
- 單例類.m文件
引入NSURLSession代理
@interface DownloadManager ()<NSURLSessionDownloadDelegate>
@end
定義全局的下載session
@implementation DownloadManager {
/// 全局下載的session
NSURLSession *_downloadSession;
}
實現獲取單例類方法
+ (instancetype)sharedManager
{
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
重寫單例實例化方法:實例化全局的下載session
提示 : delegateQueue 如果傳入nil,說明代理方法都在子線程執行
- (instancetype)init
{
if (self = [super init]) {
// 全局下載session的配置信息
NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"HM"];
// 實例化全局下載session
_downloadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
return self;
}
單例下載的主方法
- (void)downloadWithURLString:(NSString *)URLString progress:(void (^)(float))progressBlock completion:(void (^)(NSString *))completionBlock
{
// URL
NSURL *URL = [NSURL URLWithString:URLString];
// 自定義session發起和啟動任務
[[_downloadSession downloadTaskWithURL:URL] resume];
}
- NSURLSessionDownloadDelegate
監聽文件下載進度
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
// 計算進度
float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;
NSLog(@"DownloadManager 進度 %f",progress);
}
監聽文件下載完成
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
NSLog(@"DownloadManager 文件下載完成 %@",location.path);
}
- 控制器調用單例的下載方法
- (void)downloadBtnClick:(BookCell *)cell
{
// 1.獲取點擊的是第幾行cell
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// 2.取出改行cell對應的模型數據
BookModel *book = _bookList[indexPath.row];
// 3.調用單例實現下載
[[DownloadManager sharedManager] downloadWithURLString:book.path progress:^(float progress) {
// NSLog(@"VC 進度 %zd -- %f",indexPath.row,progress);
} completion:^(NSString *filePath) {
// NSLog(@"VC 下載完成 %@",filePath);
}];
}
- 小結
- 接下來要做的事情 : 把對應的cell上的下載進度回調到控制器
- 存在的問題 : 如何區別哪個進度的回調是對應哪個cell的?
- 提示 : 不能用屬性記錄外界傳入的progressBlock,后面的會覆蓋前面的
錯誤的進度回調演示
- 定義回調下載進度的Block屬性
@interface DownloadManager () <NSURLSessionDownloadDelegate>
/// 回調下載進度
@property (nonatomic,copy) void(^progressBlock)(float progress);
@end
- 調用單例下載文件的主方法時,記錄這個外界傳入的進度回調
- (void)downloadWithURLString:(NSString *)URLString progress:(void (^)(float))progressBlock completion:(void (^)(NSString *))completionBlock
{
// URL
NSURL *URL = [NSURL URLWithString:URLString];
// 自定義session發起和啟動任務
[[_downloadSession downloadTaskWithURL:URL] resume];
// 保存VC傳入的進度回調
self.progressBlock = progressBlock;
}
- 回調下載進度
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
// 計算進度
float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;
// NSLog(@"DownloadManager 進度 %f",progress);
// 把下載進度回調到控制器
if (self.progressBlock) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.progressBlock(progress);
}];
}
}
- 控制器里面調用單例下載的方法并傳入進度回調
- (void)downloadBtnClick:(BookCell *)cell
{
// 1.獲取點擊的是第幾行cell
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// 2.取出改行cell對應的模型數據
BookModel *book = _bookList[indexPath.row];
// 3.調用單例實現下載
[[DownloadManager sharedManager] downloadWithURLString:book.path progress:^(float progress) {
NSLog(@"VC 進度 %zd -- %f",indexPath.row,progress);
} completion:^(NSString *filePath) {
// NSLog(@"VC 下載完成 %@",filePath);
}];
}
- 結果分析
- 后面傳入的進度回調把前面的進度回調覆蓋了
- 解決辦法 : 使用字典制作進度回調緩存池,記錄哪個回調是屬于哪個任務的
進度回調的保存提取和執行
使用字典制作進度回調緩存池,記錄哪個回調是屬于哪個任務的
- 制作進度回調緩存池
@implementation DownloadManager {
/// 全局下載的session
NSURLSession *_downloadSession;
/// 進度回調緩存池
NSMutableDictionary *_progressBlockDict;
}
/// 單例的實例化方法
- (instancetype)init
{
if (self = [super init]) {
// 實例化全局下載session
NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"HM"];
_downloadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
// 實例化進度回調緩存池
_progressBlockDict = [[NSMutableDictionary alloc] init];
}
return self;
}
- 在單例下載文件的主方法里面,把控制器傳入的進度回調保存到緩存池
- 提示 : 以downloadTask作為key的目的是,需要在代理里面取這個回調,但是代理方法只能拿到downloadTask
- (void)downloadWithURLString:(NSString *)URLString progress:(void (^)(float))progressBlock completion:(void (^)(NSString *))completionBlock
{
// 1. URL
NSURL *URL = [NSURL URLWithString:URLString];
// 2. 自定義session發起和啟動任務
NSURLSessionDownloadTask *downloadTask = [_downloadSession downloadTaskWithURL:URL];
// 3. 把VC傳入的進度回調保存到緩存池
[_progressBlockDict setObject:progressBlock forKey:downloadTask];
// 4. 啟動任務
[downloadTask resume];
}
- 回調各自下載任務的進度
- 使用步驟 :
- 計算進度
- 從緩存池取進度回調 (downloadTask作為key)
- 把下載進度回調到控制器
- 使用步驟 :
/// 監聽文件下載進度
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
// 1. 計算進度
float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;
// NSLog(@"DownloadManager 進度 %f",progress);
// 2. 從緩存池取進度回調
void (^progressBlock)(float) = [_progressBlockDict objectForKey:downloadTask];
// 3. 把下載進度回調到控制器
if (progressBlock) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
progressBlock(progress);
}];
}
}
- 控制器里面調用單例下載的方法并傳入進度回調
- (void)downloadBtnClick:(BookCell *)cell
{
// 1.獲取點擊的是第幾行cell
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// 2.取出改行cell對應的模型數據
BookModel *book = _bookList[indexPath.row];
// 3.調用單例實現下載
[[DownloadManager sharedManager] downloadWithURLString:book.path progress:^(float progress) {
NSLog(@"VC 進度 %zd -- %f",indexPath.row,progress);
} completion:^(NSString *filePath) {
// NSLog(@"VC 下載完成 %@",filePath);
}];
}
進度展示
- 模型類增加記錄文件下載進度的屬性
/// 記錄下載進度
@property (nonatomic,assign) float downloadProgress;
- 自定義cell類中,模型的setter方法
- (void)setBook:(BookModel *)book
{
_book = book;
// 提示 : 解決cell滾動時,按鈕狀態復用的問題
// 取出按鈕,根據之前點擊時記錄的狀態,設置按鈕的文字
UIButton *btn = (UIButton *)self.accessoryView;
NSString *title = (self.book.isSelected == YES) ? @"暫停" : @"下載";
[btn setTitle:title forState:UIControlStateNormal];
self.nameLabel.text = book.name;
self.progressView.progress = book.downloadProgress;
}
- 控制器里面downloadBtnClick方法的實現
- 解決cell的復用造成的進度復用的問題
- (void)downloadBtnClick:(BookCell *)cell
{
// 1.獲取點擊的是第幾行cell
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// 2.取出該行cell對應的模型數據
BookModel *book = _bookList[indexPath.row];
// 3.調用單例實現下載
[[DownloadManager sharedManager] downloadWithURLString:book.path progress:^(float progress) {
NSLog(@"VC 進度 %zd -- %f",indexPath.row,progress);
// 如果正在下載,當滾動cell時,cell會復用;
// 提示 : indexPath不會復用,可以通過選中cell時產生的indexPath,獲取正確的cell
BookCell *updateCell = [self.tableView cellForRowAtIndexPath:indexPath];
// cell的模型變化了cell也會變化 : MVC的特點
book.downloadProgress = progress;
// 給cell賦值新的模型
updateCell.book = book;
} completion:^(NSString *filePath) {
// NSLog(@"VC 下載完成 %@",filePath);
}];
}
下載完成的回調
- 使用字典制作下載完成回調的緩存池
@implementation DownloadManager {
/// 全局下載的session
NSURLSession *_downloadSession;
/// 進度回調緩存池
NSMutableDictionary *_progressBlockDict;
/// 完成回調緩存池
NSMutableDictionary *_completionBlockDict;
}
// 單例的實例化方法
- (instancetype)init
{
if (self = [super init]) {
// 實例化全局下載session
NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"HM"];
_downloadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
// 實例化進度回調緩存池 / 完成回調緩存池
_progressBlockDict = [[NSMutableDictionary alloc] init];
_completionBlockDict = [[NSMutableDictionary alloc] init];
}
return self;
}
- 把控制器傳入的下載完成的回調保存到緩存池
- (void)downloadWithURLString:(NSString *)URLString progress:(void (^)(float))progressBlock completion:(void (^)(NSString *))completionBlock
{
// 1. URL
NSURL *URL = [NSURL URLWithString:URLString];
// 2. 自定義session發起和啟動任務
NSURLSessionDownloadTask *downloadTask = [_downloadSession downloadTaskWithURL:URL];
// 3. 把VC傳入的進度回調 / 完成回調 保存到緩存池
// 提示 : 以downloadTask作為key的目的是,需要在代理里面取這個回調,但是代理方法只有downloadTask
[_progressBlockDict setObject:progressBlock forKey:downloadTask];
[_completionBlockDict setObject:completionBlock forKey:downloadTask];
// 4. 啟動任務
[downloadTask resume];
}
- NSURLSession的下載完成的代理方法
- 把下載完成的音頻文件緩存到沙盒
- 取出下載完成回調
- 回調路徑到控制器
- 下載完成之后把相關緩存池清空
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
// NSLog(@"DownloadManager 文件下載完成 %@",location.path);
// 1.把下載完成的音頻文件緩存到沙盒
NSString *URLString = downloadTask.currentRequest.URL.absoluteString;
NSString *fileName = [URLString lastPathComponent];
NSString *savePath = [NSString stringWithFormat:@"/Users/zhangjie/Desktop/%@",fileName];
[[NSFileManager defaultManager] copyItemAtPath:location.path toPath:savePath error:NULL];
// 2.取出下載完成回調
void(^completionBlock)(NSString *) = [_completionBlockDict objectForKey:downloadTask];
// 3.回調路徑到控制器
if (completionBlock) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
completionBlock(savePath);
}];
}
// 4.下載完成之后把相關緩存池清空
[_progressBlockDict removeObjectForKey:downloadTask];
[_completionBlockDict removeObjectForKey:downloadTask];
}
判斷是否正在下載
- 思路
- 先準備下載任務緩存池
- 每創建一個下載任務,就把下載任務添加到這個緩存池
- 控制器在建立下載任務之前,先判斷要建立的下載任務在緩存池有沒有
- 如果要建立的下載任務在緩存池中有,就執行暫停.反之,就下載
- 下載任務完成或者暫停下載之后,需要把任務從緩存池移除掉
- 單例里面需要實現的
單例增加方法 判斷是否正在下載
/**
* 檢查是否正在下載
*
* @param URLString 下載地址
*
* @return 返回是否正在下載
*/
- (BOOL)checkIsDownloadingWithURLString:(NSString *)URLString;
判斷是否正在下載方法的實現
- (BOOL)checkIsDownloadingWithURLString:(NSString *)URLString
{
if ([_downlaodTaskDict objectForKey:URLString] != nil) {
return YES;
}
return NO;
}
保存下載任務到緩存池
- (void)downloadWithURLString:(NSString *)URLString progress:(void (^)(float))progressBlock completion:(void (^)(NSString *))completionBlock
{
// 1. URL
NSURL *URL = [NSURL URLWithString:URLString];
// 2. 自定義session發起和啟動任務
NSURLSessionDownloadTask *downloadTask = [_downloadSession downloadTaskWithURL:URL];
// 3. 把VC傳入的進度回調 / 完成回調 保存到緩存池
// 提示 : 以downloadTask作為key的目的是,需要在代理里面取這個回調,但是代理方法只有downloadTask
[_progressBlockDict setObject:progressBlock forKey:downloadTask];
[_completionBlockDict setObject:completionBlock forKey:downloadTask];
// 4.把下載任務添加到緩存池
[_downlaodTaskDict setObject:downloadTask forKey:URLString];
// 5. 啟動任務
[downloadTask resume];
}
下載完成之后,移除相關的緩存池
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
// NSLog(@"DownloadManager 文件下載完成 %@",location.path);
// 1.把下載完成的音頻文件緩存到沙盒
NSString *URLString = downloadTask.currentRequest.URL.absoluteString;
NSString *fileName = [URLString lastPathComponent];
NSString *savePath = [NSString stringWithFormat:@"/Users/zhangjie/Desktop/%@",fileName];
[[NSFileManager defaultManager] copyItemAtPath:location.path toPath:savePath error:NULL];
// 2.取出下載完成回調
void(^completionBlock)(NSString *) = [_completionBlockDict objectForKey:downloadTask];
// 3.回調路徑到控制器
if (completionBlock) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
completionBlock(savePath);
}];
}
// 4.下載完成之后把相關緩存池清空
[_progressBlockDict removeObjectForKey:downloadTask];
[_completionBlockDict removeObjectForKey:downloadTask];
[_downlaodTaskDict removeObjectForKey:URLString];
}
- 控制器里面,在建立下載任務之前,先判斷是否正在下載
- (void)downloadBtnClick:(BookCell *)cell
{
// 1.獲取點擊的是第幾行cell
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// 2.取出該行cell對應的模型數據
BookModel *book = _bookList[indexPath.row];
// 3.判斷是否正在下載
BOOL isDownloading = [[DownloadManager sharedManager] checkIsDownloadingWithURLString:book.path];
if (!isDownloading) {
// 3.調用單例實現下載
[[DownloadManager sharedManager] downloadWithURLString:book.path progress:^(float progress) {
NSLog(@"VC 進度 %zd -- %f",indexPath.row,progress);
// 如果正在下載,當滾動cell時,cell會復用;
// 提示 : indexPath不會復用,可以通過選中cell時產生的indexPath,獲取正確的cell
BookCell *updateCell = [self.tableView cellForRowAtIndexPath:indexPath];
// cell的模型變化了cell也會變化 : MVC的特點
book.downloadProgress = progress;
// 給cell賦值新的模型
updateCell.book = book;
} completion:^(NSString *filePath) {
NSLog(@"VC 下載完成 %@",filePath);
}];
} else {
NSLog(@"暫停");
}
}
暫停下載
- 單例準備暫停下載的主方法
/**
* 暫停下載的主方法
*
* @param URLString 暫停下載的地址
*/
- (void)pauseDownloadWithURLString:(NSString *)URLString pauseBlock:(void(^)())pauseBlock;
- 暫停下載的主方法實現
- 取出正在下載的任務
- 暫停這個正在下載的任務
- 把續傳數據緩存到沙盒
- 清空相關緩存池
- 把暫停的結果傳回vc
- (void)pauseDownloadWithURLString:(NSString *)URLString pauseBlock:(void (^)())pauseBlock
{
// 1.取出正在下載的任務
NSURLSessionDownloadTask *downloadTask = [_downlaodTaskDict objectForKey:URLString];
// 2.暫停這個正在下載的任務
if (downloadTask) {
[downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
// 3.把續傳數據緩存到沙盒
[resumeData writeToFile:[URLString appendTempPath] atomically:YES];
// 4.清空相關緩存池
[_progressBlockDict removeObjectForKey:downloadTask];
[_completionBlockDict removeObjectForKey:downloadTask];
[_downlaodTaskDict removeObjectForKey:URLString];
// 5.把暫停的結果回到到VC
if (pauseBlock) {
pauseBlock();
}
}];
}
}
- 控制器中使用單例的暫停方法
- (void)downloadBtnClick:(BookCell *)cell
{
// 1.獲取點擊的是第幾行cell
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
// 2.取出該行cell對應的模型數據
BookModel *book = _bookList[indexPath.row];
// 3.判斷是否正在下載
BOOL isDownloading = [[DownloadManager sharedManager] checkIsDownloadingWithURLString:book.path];
if (!isDownloading) {
// 3.調用單例實現下載
[[DownloadManager sharedManager] downloadWithURLString:book.path progress:^(float progress) {
NSLog(@"VC 進度 %zd -- %f",indexPath.row,progress);
// 如果正在下載,當滾動cell時,cell會復用;
// 提示 : indexPath不會復用,可以通過選中cell時產生的indexPath,獲取正確的cell
BookCell *updateCell = [self.tableView cellForRowAtIndexPath:indexPath];
// cell的模型變化了cell也會變化 : MVC的特點
book.downloadProgress = progress;
// 給cell賦值新的模型
updateCell.book = book;
} completion:^(NSString *filePath) {
NSLog(@"VC 下載完成 %@",filePath);
}];
} else {
[[DownloadManager sharedManager] pauseDownloadWithURLString:book.path pauseBlock:^{
NSLog(@"暫停成功");
}];
}
}
繼續下載
- 繼續下載思路
- 如果有續傳數據就繼續下載
- 如果沒有續傳數據就新建下載任務從頭開始下載
- (void)downloadWithURLString:(NSString *)URLString progress:(void (^)(float))progressBlock completion:(void (^)(NSString *))completionBlock
{
// 1. URL
NSURL *URL = [NSURL URLWithString:URLString];
// 獲取續傳數據
NSData *resumeData = [NSData dataWithContentsOfFile:[URLString appendTempPath]];
// 3. 自定義session發起和啟動任務
NSURLSessionDownloadTask *downloadTask;
// 3.1 如果有續傳數據就繼續下載
if (resumeData != nil) {
downloadTask = [_downloadSession downloadTaskWithResumeData:resumeData];
// 注意 : 續傳數據使用完一定要及時的移除,再次暫停時會重新的生成續傳數據!!!
[[NSFileManager defaultManager] removeItemAtPath:[URLString appendTempPath] error:NULL];
} else {
// 3.2 如果沒有續傳數據就重新下載
downloadTask = [_downloadSession downloadTaskWithURL:URL];
}
// 4. 把VC傳入的進度回調 / 完成回調 保存到緩存池
// 提示 : 以downloadTask作為key的目的是,需要在代理里面取這個回調,但是代理方法只有downloadTask
[_progressBlockDict setObject:progressBlock forKey:downloadTask];
[_completionBlockDict setObject:completionBlock forKey:downloadTask];
// 5.把下載任務添加到緩存池
[_downlaodTaskDict setObject:downloadTask forKey:URLString];
// 6. 啟動任務
[downloadTask resume];
}
來自:http://www.jianshu.com/p/7540dd1935b4
本文由用戶 AliciaPrend 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!