您的位置:首页 > 其它

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

2016-10-27 11:08 363 查看
之前用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实现的,也是最近改到项目里的,写代码就是边开发边重构吗,学以致用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐