断点续传,支持缓存和不缓存
1.导入MBProgressHUD,需要用到MBRoundProgressView这个类方法,而且在里面还要添加一个方法支持xib,
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
_progress = 0.f;
_annular = NO;
_progressTintColor = [[UIColor alloc] initWithWhite:1.f alpha:1.f];
_backgroundTintColor = [[UIColor alloc] initWithWhite:1.f alpha:.1f];
[self registerForKVO];
}
return self;
}
2.方法简单使用
#import "ViewController.h"
#import "MBProgressHUD.h"
#import "WFDownloadManager.h"
#define LVLibraryDirectory [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"AFALibraryMusic"]
//http://lhlsf61y6uods.oss-cn-hangzhou.aliyuncs.com/1605033142b6e5.mp3
@interface ViewController ()
@property (weak, nonatomic) IBOutlet MBRoundProgressView *progressView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.progressView.progressTintColor = [UIColor grayColor];
self.progressView.backgroundTintColor = [UIColor redColor];
}
- (IBAction)starbtn:(id)sender {
NSString *url = @"http://lhlsf61y6uods.oss-cn-hangzhou.aliyuncs.com/1605033142b6e5.mp3";
WFDownloadItem *item = [[WFDownloadManager shared] getDownloadItemWithUrl:url];
if (item.state == WFDownloadState_downloading) {
self.progressView.progress = item.progress;
return;
}
//下载完成的回调
WFDownloadCompletionHandler completionHandler = ^(NSString *filepath, WFDownloadState state, NSString *msg) {
if (state == WFDownloadState_pause || state == WFDownloadState_fail) {
NSLog(@"pause as fail");
}else if (state == WFDownloadState_done) {
self.progressView.hidden = YES;
}
};
WFDownloadItem *downItem = [[WFDownloadManager shared] downloadItemWithUrl:url
destinationHandler:^NSString *(NSString *suggestName) {
return LVLibraryDirectory;
//写入文件的路径
}
progressHandler:^(double progress) {
//下载进度 self.progressView.progress = progress;
}
completionHandler:completionHandler];
[downItem resume];
}
- (IBAction)pausebtn:(id)sender {
NSString *url = @"http://lhlsf61y6uods.oss-cn-hangzhou.aliyuncs.com/1605033142b6e5.mp3";
WFDownloadItem *item = [[WFDownloadManager shared] getDownloadItemWithUrl:url];
if (item.state == WFDownloadState_downloading) {
[item pause];
}
}
3.下载类方法
#import <Foundation/Foundation.h>
typedef enum{
WFDownloadState_downloading = 1,
WFDownloadState_pause,
WFDownloadState_done,
WFDownloadState_fail
}WFDownloadState;
typedef NSString* (^WFDownloadDestinationHandler) (NSString *suggestName);
typedef void (^WFDownloadProgressHandler) (double progress);
typedef void (^WFDownloadCompletionHandler) (NSString *filepath, WFDownloadState state, NSString *msg);
#pragma mark - WFDownloadItem
@interface WFDownloadItem : NSObject
@property (strong, nonatomic) NSURLSessionDataTask *downloadTask;
@property (copy, nonatomic) NSString *urlStr;
@property (strong, nonatomic) NSFileHandle *writeHandle;
@property (assign, nonatomic) BOOL allowResumeData; //是否允许缓存,默认YES
@property (assign, nonatomic) int64_t totalLength;
@property (assign, nonatomic) int64_t currentLength;
@property (copy, nonatomic) NSString *tmpFilepath;
@property (copy, nonatomic) NSString *defaultDestinationPath;
@property (assign, nonatomic) WFDownloadState state;
@property (assign, nonatomic) double progress;
@property (copy, nonatomic) WFDownloadDestinationHandler destinationHandler;
@property (copy, nonatomic) WFDownloadProgressHandler progressHandler;
@property (copy, nonatomic) WFDownloadCompletionHandler completionHandler;
- (void)resume;
- (void)pause;
@end
#pragma mark - WFDownloadManager
@interface WFDownloadManager : NSObject <NSURLSessionDataDelegate>
@property (strong, nonatomic) NSURLSession *session;
@property (strong, nonatomic) NSMutableDictionary *downloadList; //下载列表
+ (WFDownloadManager *)shared;
- (NSUInteger)getFileSizeWithPath:(NSString *)path;
- (WFDownloadItem *)getDownloadItemWithUrl:(NSString *)urlStr;
- (void)cancelAllDownloads;
- (WFDownloadItem *)downloadItemWithUrl:(NSString *)urlStr
destinationHandler:(WFDownloadDestinationHandler)destinationHandler
progressHandler:(WFDownloadProgressHandler)progressHandler
completionHandler:(WFDownloadCompletionHandler)completionHandler;
@end
#import "WFDownloadManager.h"
#import <CommonCrypto/CommonDigest.h>
#pragma mark - WFDownloadItem
@implementation WFDownloadItem
- (void)resume
{
if (_state != WFDownloadState_downloading) {
NSString *encodeStr = [_urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "].invertedSet];
NSURL *url = [NSURL URLWithString:encodeStr];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//设置请求头偏移量
_tmpFilepath = [NSString stringWithFormat:@"%@download%@.tmp", NSTemporaryDirectory(), [WFDownloadItem md5:encodeStr]];
if (_allowResumeData) {
NSString *range = [NSString stringWithFormat:@"bytes=%lu-", (unsigned long)[[WFDownloadManager shared] getFileSizeWithPath:_tmpFilepath]];
[request setValue:range forHTTPHeaderField:@"Range"];
}
_downloadTask = [[WFDownloadManager shared].session dataTaskWithRequest:request];
[[WFDownloadManager shared].downloadList setObject:self forKey:encodeStr];
_state = WFDownloadState_downloading;
[_downloadTask resume];
}
}
- (void)pause
{
if (_state == WFDownloadState_downloading) {
_state = WFDownloadState_pause;
[_downloadTask cancel];
}
}
+ (NSString*)sha1:(NSString*)input
{
const char *cStr = [input UTF8String];
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(cStr, (int32_t)strlen(cStr), digest);
NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++)
[output appendFormat:@"%02x", digest[i]];
return output;
}
+ (NSString *)md5:(NSString *)input
{
const char *cStr = [input UTF8String];
unsigned char digest[CC_MD5_DIGEST_LENGTH];
CC_MD5(cStr, (int32_t)strlen(cStr), digest);
NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
[output appendFormat:@"%02x", digest[i]];
return output;
}
@end
#pragma mark - WFDownloadManager
@implementation WFDownloadManager
+ (WFDownloadManager *)shared
{
static WFDownloadManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (manager == nil) {
manager = [[self alloc] init];
}
});
return manager;
}
- (instancetype)init
{
if (self = [super init]) {
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:self
delegateQueue:NULL];
_downloadList = [NSMutableDictionary dictionary];
}
return self;
}
- (void)cancelAllDownloads
{
for (NSString *key in _downloadList) {
WFDownloadItem *downloadItem = [_downloadList objectForKey:key];
if (downloadItem.state == WFDownloadState_downloading) {
[downloadItem pause];
}
}
[_downloadList removeAllObjects];
}
- (WFDownloadItem *)getDownloadItemWithUrl:(NSString *)urlStr
{
NSString *decodeStr = [urlStr stringByRemovingPercentEncoding];
NSString *encodeStr = [decodeStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "].invertedSet];
WFDownloadItem *item = [_downloadList objectForKey:encodeStr];
return item;
}
- (WFDownloadItem *)downloadItemWithUrl:(NSString *)urlStr destinationHandler:(WFDownloadDestinationHandler)destinationHandler progressHandler:(WFDownloadProgressHandler)progressHandler completionHandler:(WFDownloadCompletionHandler)completionHandler
{
NSString *decodeStr = [urlStr stringByRemovingPercentEncoding];//utf-8解码
NSString *encodeStr = [decodeStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "].invertedSet];
WFDownloadItem *downloadItem = [_downloadList objectForKey:encodeStr];
if (downloadItem.state == WFDownloadState_downloading) {
NSString *warningMsg = @"downloadTask is exsit!";
dispatch_async(dispatch_get_main_queue(), ^{
if (downloadItem.completionHandler) {
downloadItem.completionHandler(nil, downloadItem.state, warningMsg);
}
});
return downloadItem;
}
if (downloadItem == nil) downloadItem = [[WFDownloadItem alloc] init];
downloadItem.urlStr = decodeStr;
downloadItem.allowResumeData = YES;
downloadItem.progressHandler = progressHandler;
downloadItem.destinationHandler = destinationHandler;
downloadItem.completionHandler = completionHandler;
downloadItem.destinationHandler = destinationHandler;
return downloadItem;
}
- (NSUInteger)getFileSizeWithPath:(NSString *)path
{
NSUInteger currentSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil][NSFileSize] unsignedIntegerValue];
return currentSize;
}
#pragma mark - NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
completionHandler(NSURLSessionResponseAllow);
NSString *urlStr = dataTask.originalRequest.URL.absoluteString;
WFDownloadItem *downloadItem = [_downloadList objectForKey:urlStr];
//不允许断点续传时每次都创建新的临时文件,允许断点续传时如果临时文件不存在才创建
if (downloadItem.allowResumeData == NO || [[NSFileManager defaultManager] fileExistsAtPath:downloadItem.tmpFilepath] == NO) {
[[NSFileManager defaultManager] createFileAtPath:downloadItem.tmpFilepath contents:nil attributes:nil];
}
downloadItem.defaultDestinationPath = [NSTemporaryDirectory() stringByAppendingPathComponent:dataTask.response.suggestedFilename];
downloadItem.currentLength = (downloadItem.allowResumeData) ? [self getFileSizeWithPath:downloadItem.tmpFilepath] : 0;
downloadItem.totalLength = dataTask.response.expectedContentLength + downloadItem.currentLength;
downloadItem.writeHandle = [NSFileHandle fileHandleForWritingAtPath:downloadItem.tmpFilepath];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
NSString *urlStr = dataTask.originalRequest.URL.absoluteString;
WFDownloadItem *downloadItem = [_downloadList objectForKey:urlStr];
BOOL status = YES;
if ([dataTask.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse*)dataTask.response statusCode];
if (statusCode > 400) {
status = NO;
}
}
if (status) {
[downloadItem.writeHandle seekToEndOfFile];
[downloadItem.writeHandle writeData:data];
downloadItem.currentLength += data.length;
downloadItem.progress = (double)downloadItem.currentLength / downloadItem.totalLength;
dispatch_async(dispatch_get_main_queue(), ^{
if (downloadItem.progressHandler) {
downloadItem.progressHandler(downloadItem.progress);
}
});
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
NSString *urlStr = task.originalRequest.URL.absoluteString;
WFDownloadItem *downloadItem = [_downloadList objectForKey:urlStr];
[downloadItem.writeHandle closeFile];
[_downloadList removeObjectForKey:urlStr];
NSString *errorMsg = error.localizedDescription;
if ([task.response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse*)task.response statusCode];
if (statusCode > 400) {
errorMsg = [NSString stringWithFormat:@"HTTP status code %@", @(statusCode)];
// NSLog(@"ERROR: HTTP status code %@", @(statusCode));
}
}
NSString *destinationFilepath = nil;
if (errorMsg == nil) {
if (downloadItem.destinationHandler) {
destinationFilepath = downloadItem.destinationHandler(task.response.suggestedFilename);
}
if (destinationFilepath == nil) {
destinationFilepath = downloadItem.defaultDestinationPath;
}
NSFileManager *fm = [NSFileManager defaultManager];
if ([fm fileExistsAtPath:destinationFilepath]) {
[fm removeItemAtPath:destinationFilepath error:nil];
}
NSError *error2 = nil;
BOOL result = [fm moveItemAtPath:downloadItem.tmpFilepath toPath:destinationFilepath error:&error2];
if (result == NO) {
errorMsg = error2.localizedDescription;
[fm removeItemAtPath:downloadItem.tmpFilepath error:nil];
}
}
if (downloadItem.state != WFDownloadState_pause) {
downloadItem.state = (errorMsg == nil) ? WFDownloadState_done : WFDownloadState_fail;
}
dispatch_async(dispatch_get_main_queue(), ^{
if (downloadItem.completionHandler) {
downloadItem.completionHandler(destinationFilepath, downloadItem.state, errorMsg);
}
});
}
@end