最近闲暇时间学习了一下断点续传,下面的是采用系统提供的NSURLSession实现的。
NSURLSession实现方式
#import
typedef void (^RSBKPDownloadSizeBlock)(NSURLSessionTask* task,NSProgress* progress);
typedef void (^RSBKPDownloadComplateBlock)(NSURLSessionTask* task,NSError* error);
/**
网络请求 断点续传
*/
@interface RSNetWorkRequestBKPDownload : NSObject
/**
断点续传-下载文件 (自动开启下载,当文件被下载完成后,再起下载该文件,且保存路径不变时,会调用文件下载完成,error为canceled)
@param url 文件全路径地址 - http://......
@param path 保存文件路径(断点下载与保存路径相关)
@return 下载任务
*/
+ (NSURLSessionDataTask *)RS_DownloadURL:(NSString *)url toPath:(NSString *)path receiveResponseBlock:(RSBKPDownloadSizeBlock)receiveResponseBlock receiveDataBlock:(RSBKPDownloadSizeBlock)receiveDataBlock completeBlock:(RSBKPDownloadComplateBlock)downComplateBlock;
/*---------------------------重启--------------------------------*/
+ (void)RS_ResumeTask:(NSURLSessionTask*)task;
+ (void)RS_ResumeTaskForURL:(NSString*)url;
+ (void)RS_ResumeAllTasks;
/*-----------------------------暂停------------------------------*/
+ (void)RS_SuspendTask:(NSURLSessionTask*)task;
+ (void)RS_SuspendTaskForURL:(NSString*)url;
+ (void)RS_SuspendAllTasks;
/*----------------------------取消-------------------------------*/
+ (void)RS_CancelTask:(NSURLSessionTask*)task;
+ (void)RS_CancelTaskForURL:(NSString*)url;
+ (void)RS_CancelAllTask;
/**
销毁session,防止session对d其代理的强引用,而造成内存泄漏
*/
+ (void)RS_invalidate;
@end
#import "RSNetWorkRequestBKPDownload.h"
#import "NSString+String.h"
/**
下载信息模型
*/
@interface RSBKPModel:NSObject
@property(nonatomic,copy) NSString* url;
@property(nonatomic,copy) NSString* savePath;
@property(nonatomic,copy) RSBKPDownloadSizeBlock receiveResponseBlock;
@property(nonatomic,copy) RSBKPDownloadSizeBlock receiveDataBlock;
@property(nonatomic,copy) RSBKPDownloadComplateBlock downComplateBlock;
@property(nonatomic,strong) NSFileHandle* fileHandle;
@property(nonatomic,strong) NSProgress* progress;
@end
@implementation RSBKPModel
@end
@interface RSNetWorkRequestBKPDownload()
@property(nonatomic,strong) NSMutableDictionary* downloadInfoData;
@property(nonatomic,strong) NSMutableDictionary* downLoadingTasks;
@property(nonatomic,strong) NSURLSession* session;
@end
//static NSURLSession* session;
@implementation RSNetWorkRequestBKPDownload
+ (instancetype)share{
static RSNetWorkRequestBKPDownload* BKPDownload;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
BKPDownload = [RSNetWorkRequestBKPDownload new];
});
return BKPDownload;
}
+ (NSURLSessionDataTask*)RS_DownloadURL:(NSString *)url toPath:(NSString *)path receiveResponseBlock:(RSBKPDownloadSizeBlock)receiveResponseBlock receiveDataBlock:(RSBKPDownloadSizeBlock)receiveDataBlock completeBlock:(RSBKPDownloadComplateBlock)downComplateBlock{
return [[self share] _RS_downloadURL:url toPath:path receiveResponseBlock:receiveResponseBlock receiveDataBlock:receiveDataBlock completeBlock:downComplateBlock];
}
+ (void)RS_ResumeTask:(NSURLSessionDataTask*)task{
if (task && task.state == NSURLSessionTaskStateSuspended){
[task resume];
}
}
+ (void)RS_ResumeTaskForURL:(NSString*)url{
NSURLSessionTask* task = [[self share] downLoadingTasks][url.MD5];
[self RS_ResumeTask:task];
}
+ (void)RS_ResumeAllTasks{
[[[self share] downLoadingTasks].allValues enumerateObjectsUsingBlock:^(NSURLSessionTask* _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) {
[self RS_ResumeTask:task];
}];
}
+ (void)RS_SuspendTask:(NSURLSessionDataTask*)task{
if (task && task.state == NSURLSessionTaskStateRunning){
[task suspend];
}
}
+ (void)RS_SuspendTaskForURL:(NSString*)url{
NSURLSessionTask* task = [[self share] downLoadingTasks][url.MD5];
[self RS_SuspendTask:task];
}
+ (void)RS_SuspendAllTasks{
[[[self share] downLoadingTasks].allValues enumerateObjectsUsingBlock:^(NSURLSessionTask* _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) {
[self RS_SuspendTask:task];
}];
}
+ (void)RS_CancelTask:(NSURLSessionTask*)task{
if (task && (task.state == NSURLSessionTaskStateRunning || task.state == NSURLSessionTaskStateSuspended)){
[task cancel];
}
}
+ (void)RS_CancelTaskForURL:(NSString*)url{
NSURLSessionTask* task = [[self share] downLoadingTasks][url.MD5];
[self RS_CancelTask:task];
}
+ (void)RS_CancelAllTask{
[[[self share] downLoadingTasks].allValues enumerateObjectsUsingBlock:^(NSURLSessionTask* _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) {
[self RS_CancelTask:task];
}];
}
+ (void)RS_invalidate{
[[[RSNetWorkRequestBKPDownload share] session] invalidateAndCancel];
[RSNetWorkRequestBKPDownload share].session = nil;
}
#pragma mark - private
- (NSURLSessionDataTask *)_RS_downloadURL:(NSString *)url toPath:(NSString *)path receiveResponseBlock:(RSBKPDownloadSizeBlock)receiveResponseBlock receiveDataBlock:(RSBKPDownloadSizeBlock)receiveDataBlock completeBlock:(RSBKPDownloadComplateBlock)downComplateBlock{
NSURLSessionDataTask* urlTask = self.downLoadingTasks[url.MD5];
if (urlTask) {
return urlTask;
}
NSFileManager* fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:path]) {
[fileManager createFileAtPath:path contents:nil attributes:nil];
}
NSError* error;
NSDictionary* fileInfo = [fileManager attributesOfItemAtPath:path error:&error];
if (error) {
if (downComplateBlock) {
downComplateBlock(nil,error);
}
return nil;
}
NSFileHandle* fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
unsigned long long location = fileInfo.fileSize;
NSURLRequest* reuqest = [self _RS_initDownloadRequest:url withBytesLength:location];
NSURLSessionDataTask* task = [self.session dataTaskWithRequest:reuqest];
//创建model保存相关信息
NSProgress* progress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
progress.completedUnitCount = location;
RSBKPModel* model = [RSBKPModel new];
model.url = url;
model.savePath = path;
model.receiveResponseBlock = receiveResponseBlock;
model.receiveDataBlock = receiveDataBlock;
model.downComplateBlock = downComplateBlock;
model.fileHandle = fileHandle;
model.progress = progress;
//缓存model
[self.downloadInfoData setObject:model forKey:@(task.taskIdentifier)];
//将任务标记为正在运行
[self.downLoadingTasks setObject:task forKey:url.MD5];
[task resume];
return task;
}
- (NSURLRequest*)_RS_initDownloadRequest:(NSString*)url withBytesLength:(unsigned long long)length{
NSURL* reqUrl = [NSURL URLWithString:url];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:reqUrl];
// 设置HTTP请求头中的Range
NSString* range = [NSString stringWithFormat:@"bytes=%llu-", length];
[request setValue:range forHTTPHeaderField:@"Range"];
return request;
}
#pragma mark - NSULRSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
RSBKPModel* currentModel = self.downloadInfoData[@(dataTask.taskIdentifier)];
if (response.expectedContentLength > currentModel.progress.completedUnitCount) {
currentModel.progress.totalUnitCount = response.expectedContentLength + currentModel.progress.completedUnitCount;
if (currentModel.receiveResponseBlock) {
currentModel.receiveResponseBlock(dataTask, currentModel.progress);
}
completionHandler(NSURLSessionResponseAllow);
}else{
completionHandler(NSURLSessionResponseCancel);
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
RSBKPModel* currentModel = self.downloadInfoData[@(dataTask.taskIdentifier)];
// 指定数据的写入位置 -- 文件内容的最后面
[currentModel.fileHandle seekToEndOfFile];
// 向沙盒写入数据
[currentModel.fileHandle writeData:data];
currentModel.progress.completedUnitCount += data.length;
if (currentModel.receiveDataBlock) {
currentModel.receiveDataBlock(dataTask, currentModel.progress);
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
RSBKPModel* currentModel = self.downloadInfoData[@(task.taskIdentifier)];
[currentModel.fileHandle closeFile];
[self.downloadInfoData removeObjectForKey:task];
[self.downLoadingTasks removeObjectForKey:currentModel.url.MD5];
if (currentModel.downComplateBlock) {
currentModel.downComplateBlock(task,error);
}
}
#pragma mark - getter
- (NSURLSession *)session{
if (!_session) {
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
}
return _session;
}
- (NSMutableDictionary *)downloadInfoData{
if (!_downloadInfoData) {
_downloadInfoData = @{}.mutableCopy;
}
return _downloadInfoData;
}
/**
保存正在进行的下载任务
@return 字典
*/
- (NSMutableDictionary*)downLoadingTasks{
if (!_downLoadingTasks) {
_downLoadingTasks = @{}.mutableCopy;
}
return _downLoadingTasks;
}
@end