OC-实现下载单例类

文章目录

前言

在第一个项目中,需要实现歌曲下载的控制,简单实现如下

下载类

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];
}
相关推荐
SameX9 小时前
做了一个健康记录 App,聊聊 SwiftData + 拨轮交互的实现思路
ios
诸葛亮的芭蕉扇10 小时前
iOS视频自动全屏问题解决方案
ios·音视频
for_ever_love__12 小时前
UI学习:UITableViewCell的创建及复用机制
学习·ui·objective-c
Bug 挖掘机13 小时前
从0到1做出可复用的 iOS 自动化测试 Skill,附真机演示效果
自动化测试·测试开发·ios
掘根13 小时前
【微服务即时通讯】客户端通信连接
ios·iphone
00后程序员张13 小时前
完整指南 iOS App上架到App Store的步骤详解
macos·ios·小程序·uni-app·objective-c·cocoa·iphone
鹤卿12314 小时前
Block基础
开发语言·ios·objective-c
开开心心loky14 小时前
[OC 底层] (二)类与对象底层原理
macos·ios·objective-c·cocoa
LCG元1 天前
STM32嵌入式开发:基于LD3320的智能语音识别系统
stm32·语音识别·xcode
ZZH_AI项目交付1 天前
扫脸功能交给 SDK 后,主工程里的旧代码怎么删除
ios·app·apple