文章目录
前言
在第一个项目中,需要实现歌曲下载的控制,简单实现如下
下载类
objc
@property (nonatomic, strong)NSURLSession* session;//这个session是负责会话的工厂、任务的调度中心、网络回调的分发中心,在这里将所有的网络事件交给manager来处理
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSURLSessionDownloadTask *> *downloadTasks;//保存当前正在下载的NSURLSessionDownloadTask实例,便于实现暂停、取查询下载状态,这里每个任务都是一个网络请求
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSData *> *resumeDataMap//保存下载的断点续传数据,在iOS中,提供了cancleByProducingResumeData:接口,会生成一个二进制数据,内部包含已经下载文件内容、文件的下载进度、HTTP请求状态信息
初始化
objc
+ (instancetype)sharedManager {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[MusicDownloadManager alloc] init];
manager.resumeDataMap = [NSMutableDictionary dictionary];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];//获取一个默认的网络会话配置
manager.session = [NSURLSession sessionWithConfiguration:config delegate:manager delegateQueue:[[NSOperationQueue alloc] init]];//所有delegate的回调都在主线程操作,默认是在后台线程
manager.downloadTasks = [NSMutableDictionary dictionary];
});
return manager;
}
下载实现
objc
- (void)downloadSongWithURL:(NSURL* )url progress:(void(^)(float))progressBlock completion:(void(^)(NSURL* , NSError* ))completionBlock {
NSString* key = url.absoluteString;//获取url的绝对路径,这里如果使用url当作字典的键的话,url的比对会调用hash/equal方法来对比,之接用作键的话可能会出现错误
NSString* fileName = url.lastPathComponent;
NSString* docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:fileName];//参数YES:表示返回绝对路径
if([[NSFileManager defaultManager] fileExistsAtPath:docPath]) {
if (completionBlock) {
completionBlock([NSURL fileURLWithPath:docPath], nil);
}
return;
}//获取iOS提供的文件管理类,如果文件存在说明文件已经下载过,返回文件路径就行,不必在下载一次
NSURLSessionDownloadTask* task = nil;
NSData* resumeData = self.resumeDataMa[key];
if (resumeData) {
task = [self.session downloadTaskWithResumeData:resumeData];
[self.resumeDataMap removeObjectForKey:key];
} else {
task = [self.session downloadTaskWithURL:url];
}//查询resumeDataMap是否有未完成下载的任务,如果有就继续传输
//内部调用CFNetwork处理请求,TCP/IP,文件写入部分工作
objc_setAssociationObject(task, "progressBlock", progressBlock, OBJC_ASSOCIATION_COPY_NSNATOMIC);//每次下载进度回调
objc_setAssociationObject(task, "completionBlock", progressBlock, OBJC_ASSOCIATION_COPY_NSNATOMIC);//下载完成或失败回调
}
取消下载
objc
- (void)cancelDownloadForURL:(NSURL *)url {
NSString* key = url.absoluteString;
NSURLSessionDownloadTask *task = self.downloadTasks[key];
if (task) {
[task cancel];
[self.downloadTasks removeObjectForKey:key];
[self.resumeDataMap removeObjectForKey:key];
}
}
暂停下载
objc
- (void)pauseDownloadForURL:(NSURL *)url {
NSString* key = url.absoluteString;
NSURLSessionDownloadTask *task = self.downloadTasks[key];
if (!task) {
return;
}
[task cancelByProducingResumeData:^(NSData *resumeData) {//暂停并获取断点数据
if (resumeData) {
self.resumeDataMap[key] = resumeData;
}
[self.downloadTasks removeObjectForKey:key];
}];
}
监听下载进度
objc
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
NSLog(@"正在下载");
void(^progressBlock)(float) = objc_getAssociatedObject(downloadTask, "progressBlock");//将回调block取出
if (!progressBlock) {
return;
}
if (totalBytesExpectedToWrite > 0) {
float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;
progressBlock(progress);
}
}
实时监听下载任务的数据写入情况,当下载任务向本地临时文件写入部分数据时系统就会调用这个方法
objc
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSString *fileName = downloadTask.originalRequest.URL.lastPathComponent;
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:fileName];
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = nil;
BOOL success = [fm moveItemAtURL:location toURL:[NSURL fileURLWithPath:docPath] error:&error];//将临时文件移动到docPath
// 回调 completion,通知下载完成或者失败
void(^completionBlock)(NSURL *, NSError *) = objc_getAssociatedObject(downloadTask, "completionBlock");
if (completionBlock) {
completionBlock(success ? [NSURL fileURLWithPath:docPath] : nil, error);
}
// 移除任务
NSString *key = downloadTask.originalRequest.URL.absoluteString;
[self.downloadTasks removeObjectForKey:key];
[self.resumeDataMap removeObjectForKey:key];
}