之前用afn2.x的AFHttpOperation結合sqlite數據庫管理做了文件的斷點下載功能,之後蘋果宣布要開始限制ipv4,不過AFN的東西時給予high-level的APIs的,因此不需要修改,但是國外的開發者建議使用AFN3.0版本。
閒來無事就想重新集成一下,遷移AFN3.0的時候因為沒有了HTTPOperation,所以在修改代碼的時候全部用NSURLSessionDowonloadTask代替,不過由於之前的數據庫邏輯已經定型,且多處使用,修改起來比較復雜,DownloadTask是先下載臨時文件,下載完成後再遷移到指定文件夾,並不能通過range來指定下載位置的起始,如果用戶直接殺死App,又需要記住resumeData來重新下載,這樣在多線程同時下載多個的時候集成出了問題,可能是我邏輯沒有屢通,總覺的這樣修改起來比較費力。
最後我完全摒棄了AFN,改而實用系統提供的URLSession和URLSessionDataTask及它的代理方法來實現,這樣不需要修改現存的數據庫邏輯,只需要修改下載暫停繼續這部分的控制。
感謝大神提供:https://github.com/HHuiHao/HSDownloadManager。
首先用一個單例類來管理下載,單例類並不存儲下載內容的數據,數據只在函數之間傳遞。
/** * 單例 * * @return 返回單例對象 */ + (instancetype)sharedInstance; /** * 開啟任務下載資源 * * @param model 下載參數 * @param progressBlock 回調下載進度 * @param stateBlock 下載狀態 */ - (void)download:(DownloadModel *)model progress:(void(^)(NSInteger receivedSize, NSInteger expectedSize, CGFloat progress))progressBlock state:(void(^)(DownloadState state))stateBlock;
model包含下載文件的參數,對應創建的sqlite數據庫的表,可以在內重組文件名,從而保證拿到已下載大小的range,重新創建task繼續下載,我使用的方法也是居於上面的git連接修改的,因為要適用自己的項目,其他大概都差不多,大家可以先看看git項目,相信可以滿足大家的大部分需求。
在作者的session類中,我也添加了doanloadModel的引用,從而方便在代理方法中拿到文件路徑相關的參數做比較及存值。
@property (nonatomic, copy) NSString *fileName; @property (nonatomic,strong)DownloadModel *model;
文筆拙劣,其實寫個博客也是想為了給自己留個筆記,方便以後有用的話不需要在翻來翻去。直接貼代碼吧,還惦記著NBA總決賽呢,大家可以參考我上面給的git項目地址,作者寫的很好。
TTDownloadManager
// // TTDownloadManager.h // DownloadDemo // // Created by qihb on 16/6/3. // Copyright © 2016年 Qihb. All rights reserved. // #import#import "TTSessionModel.h" #import "DownloadModel.h" @interface TTDownloadManager : NSObject /** * 單例 * * @return 返回單例對象 */ + (instancetype)sharedInstance; /** * 開啟任務下載資源 * * @param model 下載參數 * @param progressBlock 回調下載進度 * @param stateBlock 下載狀態 */ - (void)download:(DownloadModel *)model progress:(void(^)(NSInteger receivedSize, NSInteger expectedSize, CGFloat progress))progressBlock state:(void(^)(DownloadState state))stateBlock; /** * 查詢該資源的下載進度值 * * @param url 下載地址 * * @return 返回下載進度值 */ - (CGFloat)progress:(NSString *)fileName; /** * 獲取該資源總大小 * * @param url 下載地址 * * @return 資源總大小 */ - (NSInteger)fileTotalLength:(NSString *)url; /** * 判斷該資源是否下載完成 * * @param url 下載地址 * * @return YES: 完成 */ - (BOOL)isCompletion:(NSString *)url; /** * 刪除該資源 * * @param url 下載地址 */ - (void)deleteFile:(NSString *)url; /** * 清空所有下載資源 */ - (void)deleteAllFile; /** * 暫停所有下載 */ -(void)pauseAllTask; /** * 取消下載 * */ -(void)cancelTaskWithModel:(DownloadModel *)model; @end
// // TTDownloadManager.m // DownloadDemo // // Created by qihb on 16/6/3. // Copyright © 2016年 Qihb. All rights reserved. // #import "TTDownloadManager.h" #import "NSString+Hash.h" // 緩存主目錄 #define TTCachesDirectory [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"TTCache"] // 保存文件名 #define TTFileKey(url) url.md5String // 文件的存放路徑(caches) #define TTFileFullpath(url) [TTCachesDirectory stringByAppendingPathComponent:TTFileName(url)] // 文件的已下載長度 #define TTDownloadLength(name) [[[NSFileManager defaultManager] attributesOfItemAtPath:SAVE_MODEL_PATH(name) error:nil][NSFileSize] integerValue] // 存儲文件總長度的文件路徑(caches) #define TTTotalLengthFullpath [TTCachesDirectory stringByAppendingPathComponent:@"totalLength.plist"] @interface TTDownloadManager ()/** 保存所有任務(注:用md5後作為key) */ @property (nonatomic, strong) NSMutableDictionary *tasks; /** 保存所有下載相關信息 */ @property (nonatomic, strong) NSMutableDictionary *sessionModels; @end @implementation TTDownloadManager - (NSMutableDictionary *)tasks { if (!_tasks) { _tasks = [NSMutableDictionary dictionary]; } return _tasks; } - (NSMutableDictionary *)sessionModels { if (!_sessionModels) { _sessionModels = [NSMutableDictionary dictionary]; } return _sessionModels; } static TTDownloadManager *_downloadManager; + (instancetype)allocWithZone:(struct _NSZone *)zone { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _downloadManager = [super allocWithZone:zone]; }); return _downloadManager; } - (nonnull id)copyWithZone:(nullable NSZone *)zone { return _downloadManager; } + (instancetype)sharedInstance { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _downloadManager = [[self alloc] init]; }); return _downloadManager; } /** * 創建緩存目錄文件 */ - (void)createCacheDirectory { NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:TTCachesDirectory]) { [fileManager createDirectoryAtPath:TTCachesDirectory withIntermediateDirectories:YES attributes:nil error:NULL]; } } /** * 開啟任務下載資源 */ - (void)download:(DownloadModel *)model progress:(void (^)(NSInteger, NSInteger, CGFloat))progressBlock state:(void (^)(DownloadState))stateBlock { NSString *model_url = model.model_url; NSString *fileName = [NSString stringWithFormat:@"%@.%@",model.model_md5_str,model.model_format]; if (!model_url) return; if ([self isCompletion:fileName]) { stateBlock(DownloadStateCompleted); NSLog(@"----該資源已下載完成"); return; } // 暫停 if ([self.tasks valueForKey:fileName]) { [self handle:fileName]; return; } // 創建緩存目錄文件 [self createCacheDirectory]; NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]]; NSString *toPath = SAVE_MODEL_PATH(fileName); // 創建流 NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:toPath append:YES]; // 創建請求 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:model_url]]; // 設置請求頭 NSString *range = [NSString stringWithFormat:@"bytes=%zd-", TTDownloadLength(fileName)]; [request setValue:range forHTTPHeaderField:@"Range"]; // 創建一個Data任務 NSURLSessionDataTask *task = [session dataTaskWithRequest:request]; NSTimeInterval time = [[NSDate date] timeIntervalSince1970]; NSUInteger taskIdentifier = (unsigned long)time; [task setValue:@(taskIdentifier) forKeyPath:@"taskIdentifier"]; // 保存任務 [self.tasks setValue:task forKey:fileName]; TTSessionModel *sessionModel = [[TTSessionModel alloc] init]; sessionModel.url = model_url; sessionModel.fileName = fileName; sessionModel.model = model; sessionModel.progressBlock = progressBlock; sessionModel.stateBlock = stateBlock; sessionModel.stream = stream; [self.sessionModels setValue:sessionModel forKey:@(task.taskIdentifier).stringValue]; [self start:fileName]; } - (void)handle:(NSString *)key { NSURLSessionDataTask *task = [self getTask:key]; if (task.state == NSURLSessionTaskStateRunning) { [self pause:key]; } else { [self start:key]; } } /** * 開始下載 */ - (void)start:(NSString *)key { NSURLSessionDataTask *task = [self getTask:key]; [task resume]; [self getSessionModel:task.taskIdentifier].stateBlock(DownloadStateStart); } /** * 暫停下載 */ - (void)pause:(NSString *)key { NSURLSessionDataTask *task = [self getTask:key]; [task suspend]; [self getSessionModel:task.taskIdentifier].stateBlock(DownloadStateSuspended); } /** * 根據url獲得對應的下載任務 */ - (NSURLSessionDataTask *)getTask:(NSString *)key { return (NSURLSessionDataTask *)[self.tasks valueForKey:key]; } /** * 根據url獲取對應的下載信息模型 */ - (TTSessionModel *)getSessionModel:(NSUInteger)taskIdentifier { return (TTSessionModel *)[self.sessionModels valueForKey:@(taskIdentifier).stringValue]; } /** * 判斷該文件是否下載完成 */ - (BOOL)isCompletion:(NSString *)fileName { if ([self fileTotalLength:fileName] && TTDownloadLength(fileName) == [self fileTotalLength:fileName]) { return YES; } return NO; } /** * 查詢該資源的下載進度值 */ - (CGFloat)progress:(NSString *)fileName { return [self fileTotalLength:fileName] == 0 ? 0.0 : 1.0 * TTDownloadLength(fileName) / [self fileTotalLength:fileName]; } /** * 獲取該資源總大小 */ - (NSInteger)fileTotalLength:(NSString *)fileName { return [[NSDictionary dictionaryWithContentsOfFile:TTTotalLengthFullpath][fileName] integerValue]; } #pragma mark - 刪除 /** * 刪除該資源 */ - (void)deleteFile:(NSString *)fileName { NSFileManager *fileManager = [NSFileManager defaultManager]; if ([fileManager fileExistsAtPath:SAVE_MODEL_PATH(fileName)]) { // 刪除沙盒中的資源 [fileManager removeItemAtPath:SAVE_MODEL_PATH(fileName) error:nil]; // 刪除任務 [self.tasks removeObjectForKey:fileName]; [self.sessionModels removeObjectForKey:@([self getTask:fileName].taskIdentifier).stringValue]; // 刪除資源總長度 if ([fileManager fileExistsAtPath:TTTotalLengthFullpath]) { NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:TTTotalLengthFullpath]; [dict removeObjectForKey:fileName]; [dict writeToFile:TTTotalLengthFullpath atomically:YES]; } } } /** * 暫停所有下載 */ -(void)pauseAllTask{ NSArray *allKeys = [self.tasks allKeys]; if (allKeys&&allKeys.count>0) { for (int i=0; i
TTSessionModel
// // TTSessionModel.h // DownloadDemo // // Created by qihb on 16/6/3. // Copyright © 2016年 Qihb. All rights reserved. // #import#import #import "DownloadModel.h" typedef enum { /** 未下載 */ DownloadStateUnBegin = 0, /** 下載中 */ DownloadStateStart, /** 下載暫停 */ DownloadStateSuspended, /** 下載完成 */ DownloadStateCompleted, }DownloadState; //0未下載 1正在下載 2暫停下載 3已完成下載 @interface TTSessionModel : NSObject /** 流 */ @property (nonatomic, strong) NSOutputStream *stream; /** 下載地址 */ @property (nonatomic, copy) NSString *url; @property (nonatomic, copy) NSString *fileName; @property (nonatomic,strong)DownloadModel *model; /** 獲得服務器這次請求 返回數據的總長度 */ @property (nonatomic, assign) NSInteger totalLength; /** 下載進度 */ @property (nonatomic, copy) void(^progressBlock)(NSInteger receivedSize, NSInteger expectedSize, CGFloat progress); /** 下載狀態 */ @property (nonatomic, copy) void(^stateBlock)(DownloadState state); @end
DownloadModel
// // DownloadModelList.h // Up Studio // // Created by qihb on 16/5/12. // Copyright © 2016年 gjh. All rights reserved. // #import "DBBaseModel.h" #define dbModelKeyWord @"model_md5_str" @interface DownloadModel : DBBaseModel @property(nonatomic,copy)NSString *model_md5_str; //md5串 @property(nonatomic,copy)NSString *model_image_url; //圖片遠程下載地址 @property(nonatomic,copy)NSString *model_url; //模型遠程下載地址 @property(nonatomic,copy)NSString *model_size; //文件大小 @property(nonatomic,copy)NSString *model_name; //名字 @property(nonatomic,copy)NSString *model_format; //格式 up3/stl @property(nonatomic,copy)NSString *model_type; //分類 @property(nonatomic,copy)NSString *model_image_path; //圖片本地路徑 @property(nonatomic,copy)NSString *model_path; //模型本地路徑 @property(nonatomic,copy)NSString *model_subModel_num; //子模型數量 @property(nonatomic,copy)NSString *model_payyed_flag; //付款標識 @property(nonatomic,copy)NSString *model_price; //價格 @property(nonatomic,copy)NSString *model_copyright; //版權 @property(nonatomic,copy)NSString *model_from_source; //來源 0預設 1下載 2本地保存 @property(nonatomic,copy)NSString *model_download_flag; //0未下載 1正在下載 2暫停下載 3已完成下載 @property(nonatomic,copy)NSString *model_download_percentage;//下載百分比 @property(nonatomic,copy)NSString *recent_time; //最近一次使用時間 //獲取所有下載完成的模型數據 +(NSArray *)findAllDownloadFinished; @end