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];
}
相关推荐
kevinli3 天前
available没你想象中的可靠
ios·编译原理
CodeJourney_J3 天前
如何通过SMB协议实现手机共享电脑文件
windows·ios·smb
ipad协议开发4 天前
企业微信iPad协议接口深度解析:技术原理、应用场景与开发实战
ios·企业微信·ipad
千里马-horse4 天前
Building a Simple Engine -- Mobile Development -- Platform considerations
android·ios·rendering·vulkan
systeminof5 天前
全新MacBook发布:苹果公司将 iPhone 芯片带入笔记本时代
ios·iphone
pop_xiaoli5 天前
effective-Objective-C 第四章阅读笔记
笔记·ios·objective-c·cocoa·xcode
前端不太难5 天前
Flutter 适合什么团队?RN / iOS 各自的边界在哪?
flutter·ios
带娃的IT创业者6 天前
解密OpenClaw系列10-OpenClaw系统要求
人工智能·macos·ios·objective-c·ai智能体·智能体开发·openclaw