热门标签 | 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!

 

 

 


推荐阅读
  • 本文详细介绍了JQuery Mobile框架中特有的事件和方法,帮助开发者更好地理解和应用这些特性,提升移动Web开发的效率。 ... [详细]
  • 问题场景用Java进行web开发过程当中,当遇到很多很多个字段的实体时,最苦恼的莫过于编辑字段的查看和修改界面,发现2个页面存在很多重复信息,能不能写一遍?有没有轮子用都不如自己造。解决方式笔者根据自 ... [详细]
  • 入门指南:使用FastRPC技术连接Qualcomm Hexagon DSP
    本文旨在为初学者提供关于如何使用FastRPC技术连接Qualcomm Hexagon DSP的基础知识。FastRPC技术允许开发者在本地客户端实现远程调用,从而简化Hexagon DSP的开发和调试过程。 ... [详细]
  • 本文详细介绍了如何利用 Bootstrap Table 实现数据展示与操作,包括数据加载、表格配置及前后端交互等关键步骤。 ... [详细]
  • 关于进程的复习:#管道#数据的共享Managerdictlist#进程池#cpu个数1#retmap(func,iterable)#异步自带close和join#所有 ... [详细]
  • 在iOS开发中,多线程技术的应用非常广泛,能够高效地执行多个调度任务。本文将重点介绍GCD(Grand Central Dispatch)在多线程开发中的应用,包括其函数和队列的实现细节。 ... [详细]
  • Maven + Spring + MyBatis + MySQL 环境搭建与实例解析
    本文详细介绍如何使用MySQL数据库进行环境搭建,包括创建数据库表并插入示例数据。随后,逐步指导如何配置Maven项目,整合Spring框架与MyBatis,实现高效的数据访问。 ... [详细]
  • 本文探讨了如何通过优化 DOM 操作来提升 JavaScript 的性能,包括使用 `createElement` 函数、动画元素、理解重绘事件及处理鼠标滚动事件等关键主题。 ... [详细]
  • 本文详细介绍了如何在Oracle VM VirtualBox中实现主机与虚拟机之间的数据交换,包括安装Guest Additions增强功能,以及如何利用这些功能进行文件传输、屏幕调整等操作。 ... [详细]
  • 本文探讨了一种统一的语义数据模型,旨在支持物联网、建筑及企业环境下的数据转换。该模型强调简洁性和可扩展性,以促进不同行业间的插件化和互操作性。对于智能硬件开发者而言,这一模型提供了重要的参考价值。 ... [详细]
  • 使用Echarts for Weixin 小程序实现中国地图及区域点击事件
    本文介绍了如何使用Echarts for Weixin在微信小程序中构建中国地图,并实现区域点击事件。包括效果展示、条件准备和逻辑实现的具体步骤。 ... [详细]
  • 现在越来越多的人使用IntelliJIDEA,你是否想要一个好看的IDEA主题呢?本篇博客教你如何设置一个美美哒IDEA主题,你也可以根据 ... [详细]
  • 本文介绍了 Oracle SQL 中的集合运算、子查询、数据处理、表的创建与管理等内容。包括查询部门号为10和20的员工信息、使用集合运算、子查询的注意事项、数据插入与删除、表的创建与修改等。 ... [详细]
  • 开发笔记:前端之前端初识
    开发笔记:前端之前端初识 ... [详细]
  • 使用 Mui.js 获取复选框值的方法
    本文介绍如何使用 Mui.js 框架来获取复选框的值,并通过数组进行处理和展示。 ... [详细]
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社区 版权所有