热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

详解iOS多图下载的缓存机制

1.需求点是什么?这里所说的多图下载,就是要在tableview的每一个cell里显示一张图片,而且这些图片都需要从网上下载。2.容易遇到的问题如果不知道或不使用异步
1. 需求点是什么?

 

这里所说的多图下载,就是要在tableview的每一个cell里显示一张图片,而且这些图片都需要从网上下载。

 

2. 容易遇到的问题

 

如果不知道或不使用异步操作和缓存机制,那么写出来的代码很可能会是这样:

 

cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
NSData *imageData = [NSData dataWithContentsOfURL:app.url]; cell.imageView.image = [UIImage imageWithData:imageData];

 

这样写有什么后果呢?

 

后果1:不可避免的卡顿(因为没有异步下载操作)

 

dataWithContentsOfURL:是耗时操作,将其放在主线程会造成卡顿。如果图片很多,图片很大,而且网络情况不好的话肯定会卡出翔!

 

后果2:同一图片重复下载,耗费流量和系统开销(因为没有建立缓存机制)

 

由于没有缓存机制,即使下载完成并显示了当前cell的图片,但是当该cell再一次需要显示的时候还是会下载它所对应的图片:耗费了下载流量,而且还导致重复操作。

 

很显然,要达到Tableview滚动的如丝滑般的享受必须二者兼得才可以,具体怎么做呢?

 

3. 解决方案

 

1.先看一下解决方案的流程图

要想快速看懂此图,需要先了解该流程所需的所有数据源:

 

1. 图片的URL:因为每张图片对应的URL都是唯一的,所以我们可以通过它来建立图片缓存和下载操作的缓存的键,以及拼接沙盒缓存的路径字符串。

2. 图片缓存(字典):存放于内存中;键为图片的URL,值为UIImage对象。作用:读取速度快,直接使用UIImage对象。

3. 下载操作缓存(字典):存放与内存中,键为图片的URL,值为NSBlockOperation对象。作用:用来避免对于同一张图片还要开启多个下载线程。

4. 沙盒缓存(文件路径对应NSData):存放于磁盘中,位于Cache文件夹内,路径为“Cache/图片URL的最后的部分”,值为NSData对象(将UIImage转化为NSData才能写入磁盘里)。作用:程序断网,再次启动也可以直接在磁盘中拿到图片。

 

2. 再看一下解决方案的代码

 

2.1图片缓存,下载操作缓存,沙盒缓存路径

 

/**
 *  存放所有下载完的图片
 */@property (nonatomic, strong) NSMutableDictionary *images;/**
 *  存放所有的下载操作(key是url,value是operation对象)
 */@property (nonatomic, strong) NSMutableDictionary *operations;/**
 *  拼接Cache文件夹的路径与url最后的部分,合并成唯一约定好的缓存路径
 */#define CachedImageFile(url) 

[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
NSUserDomainMask, YES) lastObject]
stringByAppendingPathComponent:[url lastPathComponent]]

 

2.2 图片下载之前的查询缓存部分:

 

//先从images缓存中取出图片url对应的UIImage
UIImage *image = self.images[app.icon];  
if (image) {
//存在:说明图片已经下载成功,并缓存成功)    cell.imageView.image = image; } else {
// 不存在:说明图片并未下载成功过,或者成功下载但是在images里缓存失
败,需要在沙盒里寻找对于的图片 // 获得url对于的沙盒缓存路径     NSString *file = CachedImageFile(app.icon);  
     // 先从沙盒中取出图片     NSData *data = [NSData dataWithContentsOfFile:file];    
     if (data) {      
     //data不为空,说明沙盒中存在这个文件         cell.imageView.image = [UIImage imageWithData:data];     } else {// 反之沙盒中不存在这个文件      // 在下载之前显示占位图片        cell.imageView.image =
        [UIImage imageNamed:@"placeholder"];// 下载图片         [self download:app.icon indexPath:indexPath];     } }

 

2.3 图片的下载部分:

 

/**
 *  下载图片
 *  @param imageUrl 图片的url
 */- (void)download:(NSString *)imageUrl indexPath:(NSIndexPath *)
indexPath {    
// 取出当前图片url对应的下载操作(operation对象)    NSBlockOperation *operation = self.operations[imageUrl];  
 if (operation) return;    
 // 创建操作,下载图片    __weak typeof(self) appsVc = self;    operation = [NSBlockOperation blockOperationWithBlock:^{      
      NSURL *url = [NSURL URLWithString:imageUrl];    
      NSData *data = [NSData dataWithContentsOfURL:url];// 下载        UIImage *image = [UIImage imageWithData:data]; // NSData
       -> UIImage        // 回到主线程        [[NSOperationQueue mainQueue] addOperationWithBlock:^{    
          if (image) {
          // 如果存在图片(下载完成),存放图片到图片缓存字典
          中                appsVc.images[imageUrl] = image;
          //将图片存入沙盒中           //1. 先将图片转化为NSData                NSData *data = UIImagePNGRepresentation
          (image);
          //2.  再生成缓存路径                            [data writeToFile:CachedImageFile(imageUrl)
          atomically:YES];            } // 从字典中移除下载操作 (保证下载失败后,
          能重新下载)            [appsVc.operations removeObjectForKey:imageUrl];
          // 刷新当前表格,减少系统开销            [appsVc.tableView reloadRowsAtIndexPaths:@
          [indexPath]
               withRowAnimation:UITableViewRowAnimationNone];        }];    }];    
   // 添加下载操作到队列中    [self.queue addOperation:operation];
   // 将当前下载操作添加到下载操作缓存中 (为了解决重复下载)    self.operations[imageUrl] = operation; }

 

3. 有哪些点是值得注意的?

 

要说值得注意的地方,还是离不开对于缓存内容的添加和删除操作。

 

3.1 关于图片缓存:


很简单,成功下载,拿到了图片,就将图片添加到图片缓存中;下载失败,什么都不做,反正没有图。在这种机制下,就没有删除缓存里某个图片项的情况,因为图片缓存永远不会出现重复添加多个相同图片的情况,缓存中只要有一张对应的图,就直接拿去用了,不会去再下载了。

 

3.2 关于沙盒缓存:


同样地,对于沙盒缓存也是一个道理:有图就将其转化为NSData,写入磁盘,并对应唯一的路径,没有图就不写。所以即使是要下载相同的图片,因为当前url对应的沙盒路径已经存在文件了,所以直接拿就可以了,不会再下载。

 

但是!


下载操作缓存是不同的!

 

3.3 关于下载操作缓存


我们需要在下载回调完成后,立即将当前的下载操作从下载操作缓存中删去!


因为要避免下载失败后,无法再次下载的情况的发生!

 

为什么呢?

注意一下将下载操作加入到下载操作缓存的时机:


是在下载开始的那一刻而不是下载成功的那一刻!

 

如果在下载开始的那一刻加入到缓存中的话,这个缓存信息就包括两个情况:下载成功和下载失败:

 

  • 如果未来下载成功了,那么我们就不会来到判断当前下载操作是否在下载操作缓存这一步,在这之前直接就可以拿图去用了,下载操作是否存在下载操作缓存里并没有什么影响。

     

  • 但是!如果未来下载失败了,那就肯定不会有对应的图片缓存和沙盒缓存,也就肯定会来到判断当前的下载操作是否在下载操作缓存里这一步。不幸的是,因为没有被删去,它是存在的。存在的话就不做任何其他操作,放任自流,导致曾经下载失败的图片永远不会再次下载。

 

忘了那段代码了么?回看一下代码(看我多好):

 

NSBlockOperation *operation = self.operations[imageUrl]; 
if (operation) return;//转身就走,毫不留情

 

因此,无论下载成功或是失败,在图片下载的回调里都要将当前的下载操作从下载操作队列中移走:用来保证如果下载失败了,就可以重新开启对应的下载

操作进行下载,逻辑上更加严谨。

  4. 最后的话

 

异步+缓存这两个机制双剑合璧的话会对程序新能带来很大的改观。这应该app开发进阶的必经之路。

 

小码哥讲述的这套流程还算比较完整的了,更重要的还是学习其中的思想:

 

  1. 将缓存分级:内存缓存,沙盒缓存,下载操作缓存。

  2. 而且还要经常使用二分法,将我们的逻辑考虑得滴水不漏。
    如果我们没有认识到将下载操作添加到下载操作缓存的时机是包含下载成功和下载失败两个情况,那么就不会考虑到即时要将下载操作从下载操作缓存中删去的操作,很容易引起bug。所以在以后的开发中,成功和失败两个情况都要考虑进去,也就是说有if一定要有else!

 

 

 


推荐阅读
  • ElasticSearch 集群监控与优化
    本文详细介绍了如何有效地监控 ElasticSearch 集群,涵盖了关键性能指标、集群健康状况、统计信息以及内存和垃圾回收的监控方法。 ... [详细]
  • 深入剖析JVM垃圾回收机制
    本文详细探讨了Java虚拟机(JVM)中的垃圾回收机制,包括其意义、对象判定方法、引用类型、常见垃圾收集算法以及各种垃圾收集器的特点和工作原理。通过理解这些内容,开发人员可以更好地优化内存管理和程序性能。 ... [详细]
  • CentOS 7.6环境下Prometheus与Grafana的集成部署指南
    本文旨在提供一套详细的步骤,指导读者如何在CentOS 7.6操作系统上成功安装和配置Prometheus 2.17.1及Grafana 6.7.2-1,实现高效的数据监控与可视化。 ... [详细]
  • 深入解析BookKeeper的设计与应用场景
    本文介绍了由Yahoo在2009年开发并于2011年开源的BookKeeper技术。BookKeeper是一种高效且可靠的日志流存储解决方案,广泛应用于需要高性能和强数据持久性的场景。 ... [详细]
  • 本文介绍如何从JSON格式的文件中提取数据并将其分配给Bash脚本中的变量。我们将探讨具体的命令和工具,帮助你高效地完成这一任务。 ... [详细]
  • 并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
    Java并发编程实践目录并发编程01——ThreadLocal并发编程02——ConcurrentHashMap并发编程03——阻塞队列和生产者-消费者模式并发编程04——闭锁Co ... [详细]
  • 优化Flask应用的并发处理:解决Mysql连接过多问题
    本文探讨了在Flask应用中通过优化后端架构来应对高并发请求,特别是针对Mysql 'too many connections' 错误的解决方案。我们将介绍如何利用Redis缓存、Gunicorn多进程和Celery异步任务队列来提升系统的性能和稳定性。 ... [详细]
  • 本文深入探讨了MySQL中常见的面试问题,包括事务隔离级别、存储引擎选择、索引结构及优化等关键知识点。通过详细解析,帮助读者在面对BAT等大厂面试时更加从容。 ... [详细]
  • 本文档汇总了Python编程的基础与高级面试题目,涵盖语言特性、数据结构、算法以及Web开发等多个方面,旨在帮助开发者全面掌握Python核心知识。 ... [详细]
  • RedHat 系统下配置国内 YUM 源以替代官方收费源的方法
    本文详细介绍如何在 RedHat Linux 中安装并配置 YUM 包管理器,并通过使用国内镜像源来解决因未购买官方服务而导致的更新源限制问题。 ... [详细]
  • 本文深入探讨了Memcached的内存管理机制,特别是其采用的Slab Allocator技术。该技术通过预分配不同大小的内存块来有效解决内存碎片问题,并确保高效的数据存储与检索。文中详细描述了Slab Allocator的工作原理、内存分配流程以及相关的优化策略。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 本文将详细介绍多个流行的 Android 视频处理开源框架,包括 ijkplayer、FFmpeg、Vitamio、ExoPlayer 等。每个框架都有其独特的优势和应用场景,帮助开发者更高效地进行视频处理和播放。 ... [详细]
  • 对于许多初学者而言,遇到总线错误(bus error)或段错误(segmentation fault/core dump)是极其令人困扰的。本文详细探讨了这两种错误的成因、表现形式及解决方法,并提供了实用的调试技巧。 ... [详细]
  • 本文详细对比了Windows 7家庭高级版与旗舰版之间的主要区别,包括技术支持期限、硬件兼容性及特色功能等方面。 ... [详细]
author-avatar
倪思慧1888
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有