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];
}
相关推荐
HouWan33 分钟前
SwiftUI 小技巧:如何读取 SwiftUI Font 的详细信息
ios·swiftui·apple
李游Leo1 小时前
HarmonyOS 7 实战开发 05:把页面做到可上线状态
ios·harmonyos
用户2181697049304 小时前
iOS 动画二 CAGradientLayer颜色渐变 CATransformLayer立体效果
objective-c
终端安全笔记6 小时前
描述文件、企业证书、MDM 不是三个名字,是三层
android·ios·智能手机
传奇开心果编程7 小时前
【SwiftUI入门练中学】第1课 从零开始
学习·ui·ios·swiftui·swift
用户2181697049307 小时前
iOS 动画 一 CABasicAnimation和CAKeyFrameAnimation
objective-c
丿Wayne12 小时前
iOS Token 刷新实战(Actor 避免并发重复刷新)
ios·swift·token·actor·swift并发
终端安全笔记1 天前
iOS 27 给了租赁一个新工具,但它只认受监督的设备
android·网络·安全·ios
大熊猫侯佩1 天前
独立 App 首发完成 iPhone Duo 展开态适配
ios·swiftui·swift