先上楼主项目已经实现效果图
此demo实现效果图
文件断点下载 实现原理
主要是运用AFNetworking实现,对AFNetworking 进行二次封装。项目要求,断网或者下载失败,下次下载时,都需要从已经下载完毕的标志位起开始下载。
一、管理类DownloadFile
typedef void(^ProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
typedef void(^SuccessBlock)(NSString *_Nonnull filePath);
typedef void(^FailBlock)(void);
@interface DownloadFile : NSObject
//单例方法
+ (nonnull DownloadFile *)sharedDownloader;
//下载文件
- (void)downloadFileWithURL:(nonnull NSString *)url
progress:(nullable ProgressBlock) progressBlock
success:(nullable SuccessBlock)successBlock
fail:(nullable FailBlock)failBlock;
//暂停下载
- (void)pauseDownloadFile:(nonnull NSString *)url;
//继续下载
//- (void)resume:(nonnull NSString *)url;
@end
二、DownloadFile.m的实现
#import "DownloadFile.h"
#import "AFNetworking.h"
#import <CommonCrypto/CommonDigest.h>
@interface DownloadFile()
@property(nonatomic,strong)NSMutableDictionary *URLTasks;
@end
@implementation DownloadFile
+ (nonnull DownloadFile *)sharedDownloader {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
- (id)init {
if(self = [super init]) {
_URLTasks = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)downloadFileWithURL:(nonnull NSString *)url
progress:(nullable ProgressBlock) progressBlock
success:(nullable SuccessBlock)successBlock
fail:(nullable FailBlock)failBlock {
//1.判断cache目录是否有缓存的文件,有的话需要续传
//将url字符串转成md5字符串,作为文件名存储本地
NSString *filename = [self cachedFileNameForKey:url];
//取得存储路径:cache
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [cachesPath stringByAppendingPathComponent:filename];
//2.下载操作
NSURL *URL = [NSURL URLWithString:url];
//默认配置
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = nil;
//3.判断是否有缓存文件,如果存在,则续传,不存在则重新下载
if(![NSFileManager.defaultManager fileExistsAtPath:filePath]) {
//创建重新下载任务对象
downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
// @property int64_t totalUnitCount; 需要下载文件的总大小
// @property int64_t completedUnitCount; 当前已经下载的大小
dispatch_async(dispatch_get_main_queue(), ^{
//回调进度block
if(progressBlock) {
progressBlock(downloadProgress.completedUnitCount,downloadProgress.totalUnitCount);
}
});
} destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
//存储到documents目录
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [cachesPath stringByAppendingPathComponent:response.suggestedFilename];
return [NSURL fileURLWithPath:path];
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
if(error) { //下载失败
NSData *resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
[self resumeDataToDisk:resumeData url:url];
if(failBlock) {
failBlock();
}
} else {
// filePath就是你下载文件的位置
NSString *file = [filePath path];
if(successBlock) {
successBlock(file);
}
}
[self.URLTasks removeObjectForKey:url];
}];
} else {
//存在,续传上次下载的文件
NSData *resumeData = [NSData dataWithContentsOfFile:filePath];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
if(!resumeData){
return;
};
downloadTask = [manager downloadTaskWithResumeData:resumeData progress:^(NSProgress * _Nonnull downloadProgress) {
dispatch_async(dispatch_get_main_queue(), ^{
//回调进度block
if(progressBlock) {
progressBlock(downloadProgress.completedUnitCount,downloadProgress.totalUnitCount);
}
});
} destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
//存储到documents目录
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [cachesPath stringByAppendingPathComponent:response.suggestedFilename];
return [NSURL fileURLWithPath:path];
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
if(error) { //下载失败
NSData *resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
[self resumeDataToDisk:resumeData url:url];
if(failBlock) {
failBlock();
}
} else {
// filePath就是你下载文件的位置
NSString *file = [filePath path];
if(successBlock) {
successBlock(file);
}
}
[self.URLTasks removeObjectForKey:url];
}];
}
[downloadTask resume];
self.URLTasks[url] = downloadTask;
}
//暂停下载
- (void)pauseDownloadFile:(nonnull NSString *)url {
NSURLSessionDownloadTask *task = self.URLTasks[url];
[task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
[self resumeDataToDisk:resumeData url:url];
}];
}
//获得已经下载文件数据
- (void)resumeDataToDisk:(NSData *_Nonnull)resumeData url:(NSString *_Nonnull)url {
NSString *filename = [self cachedFileNameForKey:url];
//取得存储路径:documents
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [cachesPath stringByAppendingPathComponent:filename];
[resumeData writeToFile:filePath atomically:YES];
}
//将字符串转成md5
- (NSString *)cachedFileNameForKey:(NSString *)key {
const char *str = [key UTF8String];
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], [[key pathExtension] isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", [key pathExtension]]];
return filename;
}
@end
三、界面的实现
#import "ViewController.h"
#import "DownloadFile.h"
#import "FileCell.h"
@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic,strong) NSArray *data;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.data = @[
@"https://download.alicdn.com/dingtalk-desktop/mac_dmg/Release/DingTalk_v3.3.3.dmg",
@"http://dldir1.qq.com/qqfile/QQforMac/QQ_V5.4.1.dmg",
@"http://xiazai.mycleanmymac.com/trial/CleanMyMac3.5_probation.dmg"
];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.data.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
FileCell *cell = [tableView dequeueReusableCellWithIdentifier:@"downloadCell" forIndexPath:indexPath];
cell.url = self.data[indexPath.row];
return cell;
}
@end
四、cell 的实现
#import "FileCell.h"
#import "DownloadFile.h"
@implementation FileCell
- (IBAction)startAction:(id)sender {
[[DownloadFile sharedDownloader] downloadFileWithURL:self.url progress:^(NSInteger receivedSize, NSInteger expectedSize) {
progressView.progress = 1.0 * receivedSize/expectedSize;
progressLabel.text = [NSString stringWithFormat:@"%.1f%%",progressView.progress*100];
} success:^(NSString * _Nonnull filePath) {
NSLog(@"下载完成:%@",filePath);
} fail:^{
}];
}
- (IBAction)pauseAction:(id)sender {
[[DownloadFile sharedDownloader] pauseDownloadFile:self.url];
}
@end
五、题外
项目中实现 核心是差不多的,有什么问题可下方留言,不足之处,还请多多指教。哈哈哈。
另外关于多文件断点上传,也有实现,相对比较简单些。就不写了~ 楼主项目中要求是 每次上传之前都会请求下服务器该文件已经上传了多少size。然后减去该文件已经上传size 上传剩余的~
另外,没有实现下载的最大并发数,写成并行队列。。。。不知道有木有已经实现的宝宝~ 欢迎留言~~ ~