文件断点下载(NSURLSessionDataTask)的使用

来源:互联网 发布:深圳蜂窝网络是培训 编辑:程序博客网 时间:2024/06/13 09:43

之前用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。
首先用一个单例类来管理下载,单例类并不存储下载内容的数据,数据只在函数之间传递。

@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;

model包含下载文件的参数,对应创建的sqlite数据库的表,可以在内重组文件名,从而保证拿到已下载大小的range,重新创建task继续下载,我使用的方法也是居于上面的Git连接修改的,因为要适用自己的项目,其他大概都差不多,大家可以先看看git项目,相信可以满足大家的大部分需求。

在作者的session类中,我也添加了doanloadModel的引用,从而方便在代理方法中拿到文件路径相关的参数做比较及存值。

@property (nonatomic, copy) NSString *fileName;@property (nonatomic,strong)DownloadModel *model;

文笔拙劣,其实写个博客也是想为了给自己留个笔记,方便以后有用的话不需要在翻来翻去。直接贴代码吧,大家可以参考我上面给的git项目地址,作者写的很好。
TTDownloadManager

////  TTDownloadManager.h//  DownloadDemo////  Created by qihb on 16/6/3.//  Copyright © 2016年 Qihb. All rights reserved.//#import <Foundation/Foundation.h>#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

.m

////  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 ()<NSCopying, NSURLSessionDelegate>/** 保存所有任务(注:用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<allKeys.count; i++) {            NSString *key = [allKeys objectAtIndex:i];            NSURLSessionDataTask *task = [self getTask:key];            [task suspend];            TTSessionModel *sessionModel =[self getSessionModel:task.taskIdentifier];            sessionModel.model.model_download_flag = @"2";            [sessionModel.model saveOrUpdate];        }    }}/** *  取消下载 * */-(void)cancelTaskWithModel:(DownloadModel *)model{    NSString *key = [NSString stringWithFormat:@"%@.%@",model.model_md5_str,model.model_format];    NSURLSessionDataTask *task = [self getTask:key];    [task cancel];    [self deleteFile:key];}#pragma mark - 代理#pragma mark NSURLSessionDataDelegate/** * 接收到响应 */- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{    TTSessionModel *sessionModel = [self getSessionModel:dataTask.taskIdentifier];    // 打开流    [sessionModel.stream open];    // 获得服务器这次请求 返回数据的总长度    NSInteger totalLength = [response.allHeaderFields[@"Content-Length"] integerValue] + TTDownloadLength(sessionModel.fileName);    sessionModel.totalLength = totalLength;    // 存储总长度    NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:TTTotalLengthFullpath];    if (dict == nil) dict = [NSMutableDictionary dictionary];    dict[sessionModel.fileName] = @(totalLength);    [dict writeToFile:TTTotalLengthFullpath atomically:YES];    // 接收这个请求,允许接收服务器的数据    completionHandler(NSURLSessionResponseAllow);}/** * 接收到服务器返回的数据 */- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{    TTSessionModel *sessionModel = [self getSessionModel:dataTask.taskIdentifier];    // 写入数据    [sessionModel.stream write:(uint8_t *)data.bytes maxLength:data.length];    // 下载进度    NSUInteger receivedSize = TTDownloadLength(sessionModel.fileName);    NSUInteger expectedSize = sessionModel.totalLength;    CGFloat progress = 1.0 * receivedSize / expectedSize;    sessionModel.progressBlock(receivedSize, expectedSize, progress);}/** * 请求完毕(成功|失败) */- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{    TTSessionModel *sessionModel = [self getSessionModel:task.taskIdentifier];    if (!sessionModel) return;    if ([self isCompletion:sessionModel.fileName]) {        // 下载完成        sessionModel.stateBlock(DownloadStateCompleted);    } else if (error){        if (error.code == NSURLErrorTimedOut||error.code == NSURLErrorNetworkConnectionLost) {            sessionModel.stateBlock(DownloadStateSuspended);        }else{        // 下载失败            sessionModel.stateBlock(DownloadStateUnBegin);        }    }    // 关闭流    [sessionModel.stream close];    sessionModel.stream = nil;    // 清除任务    [self.tasks removeObjectForKey:sessionModel.fileName];    [self.sessionModels removeObjectForKey:@(task.taskIdentifier).stringValue];}@end

TTSessionModel

////  TTSessionModel.h//  DownloadDemo////  Created by qihb on 16/6/3.//  Copyright © 2016年 Qihb. All rights reserved.//#import <Foundation/Foundation.h>#import <UIKit/UIKit.h>#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

downloadModel的部分大家可以参照我之前的博客,数据库DataBaseQueue多线程安全的博客,这个就是继承DBBaseModel实现的,也是最近改到项目里的,写代码就是边开发边重构吗,学以致用。

1 0