https://github.com/potato512/SYDemo_SYDownloadTask
在网络处理中,特别是对文件进行下载操作时,可能会出现网络异常导致的下载中断,或是设备电池没有电时的中断,又或是人为的中断。为了避免从头开始进行下载,即从下载中断点继续开始下载,这时候我们就需要使用到断点续传技术。
在iOS中,我们可以使用NSURLSession,NSURLSessionDownloadTask进行继点续传的处理。
在实现过程中有两点需要注意:
1、断点续传过程中,中断时要记录中断时的信息,以便重新进行下载时可以从中断点开始下载;
2、断点续传结束后,需要将文件保存在指定目录中,以便于后续的操作;
具体实现代码如下:
1、封装断点续传下载类
// .h文件
#import <Foundation/Foundation.h>
@interface SYDownloadTask : NSObject
/**
* 下载区域
*/
@property (nonatomic, strong, readonly) NSData *resumeData;
/**
* 开始下载
*
* @param url 下载地址
* @param complete 下载回调
*/
- (void)downloadStartWithURL:(NSString *)url complete:(void (^)(BOOL isFinish, NSString *filePath, long long downloadBytes, long long totalBytes))complete;
/**
* 继续下载
*
* @param data 下载区域
* @param complete 下载回调
*/
- (void)downloadStartWithData:(NSData *)data complete:(void (^)(BOOL isFinish, NSString *filePath, long long downloadBytes, long long totalBytes))complete;
/**
* 停止,或暂停下载
*/
- (void)downloadStop;
@end
// .m文件
#import "SYDownloadTask.h"
@interface SYDownloadTask () <NSURLSessionDownloadDelegate>
@property (nonatomic, strong) NSData *downloadData;
@property (nonatomic, copy) void (^DownloadComplete)(BOOL isFinish, NSString *filePath, long long downloadBytes, long long totalBytes);
@property (nonatomic, assign) BOOL isFinish;
@property (nonatomic, assign) long long downloadBytes;
@property (nonatomic, assign) long long totalBytes;
@property (nonatomic, strong) NSURLSessionDownloadTask *dataTask;
@property (nonatomic, strong) NSURLSession *dataSession;
@end
@implementation SYDownloadTask
#pragma mark - getter
- (NSURLSession *)dataSession
{
if (!_dataSession)
{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
_dataSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
}
return _dataSession;
}
- (NSData *)resumeData
{
return self.downloadData;
}
#pragma mark - 方法
/**
* 开始下载
*
* @param url 下载地址
* @param complete 下载回调
*/
- (void)downloadStartWithURL:(NSString *)url complete:(void (^)(BOOL isFinish, NSString *filePath, long long downloadBytes, long long totalBytes))complete
{
if (complete)
{
self.DownloadComplete = [complete copy];
}
NSURL *urlStr = [NSURL URLWithString:url];
self.dataTask = [self.dataSession downloadTaskWithURL:urlStr];
[self.dataTask resume];
}
/**
* 继续下载
*
* @param data 下载区域
* @param complete 下载回调
*/
- (void)downloadStartWithData:(NSData *)data complete:(void (^)(BOOL isFinish, NSString *filePath, long long downloadBytes, long long totalBytes))complete
{
if (complete)
{
self.DownloadComplete = [complete copy];
}
self.dataTask = [self.dataSession downloadTaskWithResumeData:data];
[self.dataTask resume];
}
/**
* 停止,或暂停下载
*/
- (void)downloadStop
{
__weak typeof(self) weakSelf = self;
[self.dataTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
weakSelf.downloadData = resumeData;
}];
}
#pragma mark - NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
self.isFinish = YES;
// 将文件默认下载到我们的tmp文件夹中,我们还需要将该文件移动到我们的cache当中
NSString *destPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [destPath stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:fileName] error:nil];
// 下载完成
if (self.DownloadComplete)
{
self.DownloadComplete(self.isFinish, fileName, self.downloadBytes, self.totalBytes);
}
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
self.downloadBytes = totalBytesWritten;
self.totalBytes = totalBytesExpectedToWrite;
// 正在下载
if (self.DownloadComplete)
{
self.DownloadComplete(self.isFinish, nil, self.downloadBytes, self.totalBytes);
}
}
@end
2、使用断点续传下载类
// 导入头文件
#import "SYDownloadTask.h"
// 定义成属性
@property (nonatomic, strong) SYDownloadTask *downloadTask;
// 使用,即实例化,或开始,暂停下载,继续下载
- (void)buttonClick:(UIButton *)button
{
if (button.selected)
{
if (self.downloadTask)
{
[self.downloadTask downloadStop];
}
}
else
{
__weak typeof(self) weakSelf = self;
if (self.downloadTask)
{
[self.downloadTask downloadStartWithData:self.downloadTask.resumeData complete:^(BOOL isFinish, NSString *filePath, long long downloadBytes, long long totalBytes) {
NSLog(@"1 下载状态:%@", (isFinish ? @"下载完成" : @"正在下载"));
NSLog(@"1 下载文件路径:%@", filePath);
NSLog(@"1 已下载:%f%%(downloadBytes = %f, totalBytes = %f)", (float)(downloadBytes)/(float)(totalBytes), (float)downloadBytes, (float)totalBytes);
weakSelf.progressView.progress = (float)(downloadBytes)/(float)(totalBytes);
if (isFinish)
{
button.selected = NO;
}
}];
}
else
{
NSString *string = @"http://sohu.vodnew.lxdns.com/193/142/tas6Zt4gReWdeh2eiBm7yD.mp4";
// NSString *string = @"http://www.68mtv.com/index/down/id/61109";
// NSString *string = @"http://imgsrc.baidu.com/baike/pic/item/203fb80e7bec54e7c5cda6b3bd389b504fc26a7e.jpg";
self.downloadTask = [[SYDownloadTask alloc] init];
[self.downloadTask downloadStartWithURL:string complete:^(BOOL isFinish, NSString *filePath, long long downloadBytes, long long totalBytes) {
NSLog(@"2 下载状态:%@", (isFinish ? @"下载完成" : @"正在下载"));
NSLog(@"2 下载文件路径:%@", filePath);
NSLog(@"2 已下载:%f%%(downloadBytes = %f, totalBytes = %f)", (float)(downloadBytes)/(float)(totalBytes), (float)downloadBytes, (float)totalBytes);
weakSelf.progressView.progress = (float)(downloadBytes)/(float)(totalBytes);
if (isFinish)
{
button.selected = NO;
}
}];
}
}
button.selected = !button.selected;
}