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!

 

 

详解iOS多图下载的缓存机制的更多相关文章

  1. 详解iOS开发之自定义View

    iOS开发之自定义View是本文要将介绍的内容,iOS SDK中的View是UIView,我们可以很方便的自定义一个View.创建一个 Window-based Application程序,在其中添加 ...

  2. 深拷贝与浅拷贝(mutableCopy与Copy)详解 iOS

    深拷贝与浅拷贝(mutableCopy与Copy)详解 iOS ios中并不是所有的对象都支持copy,mutableCopy,遵守NSCopying 协议的类可以发送copy消息,遵守NSMutab ...

  3. Java开源生鲜电商平台-盈利模式详解(源码可下载)

    Java开源生鲜电商平台-盈利模式详解(源码可下载) 该平台提供一个联合买家与卖家的一个平台.(类似淘宝购物,这里指的是食材的购买.) 平台有以下的盈利模式:(类似的平台有美菜网,食材网等) 1. 订 ...

  4. Python绘制六种可视化图表详解,三维图最炫酷!你觉得呢?

    Python绘制六种可视化图表详解,三维图最炫酷!你觉得呢? 可视化图表,有相当多种,但常见的也就下面几种,其他比较复杂一点,大都也是基于如下几种进行组合,变换出来的.对于初学者来说,很容易被这官网上 ...

  5. 详解 QT 源码之 Qt 事件机制原理

    QT 源码之 Qt 事件机制原理是本文要介绍的内容,在用Qt写Gui程序的时候,在main函数里面最后依据都是app.exec();很多书上对这句的解释是,使 Qt 程序进入消息循环.下面我们就到ex ...

  6. 转载文章-----Rational Rose2007(v7.0)下载地址、安装及激活详解教程(图)

    转载地址:http://www.cnblogs.com/leaven/p/3718361.html 最近需要画uml图,之前用的是Rose 2003版的,由于好久没进去了,结果发现原来的激活又失效了, ...

  7. Rational Rose2007(v7.0)下载地址、安装及激活详解教程(图)

    http://blog.csdn.net/skl_tz/article/details/8925152 最近需要画uml图,之前用的是Rose 2003版的,由于好久没进去了,结果发现原来的激活又失效 ...

  8. JQuery自定义插件详解之Banner图滚动插件

      前  言 JRedu JQuery是什么相信已经不需要详细介绍了.作为时下最火的JS库之一,JQuery将其"Write Less,Do More!"的口号发挥的极致.而帮助J ...

  9. 【流媒体开发】VLC Media Player - Android 平台源码编译 与 二次开发详解 (提供详细800M下载好的编译源码及eclipse可调试播放器源码下载)

    作者 : 韩曙亮  博客地址 : http://blog.csdn.net/shulianghan/article/details/42707293 转载请注明出处 : http://blog.csd ...

随机推荐

  1. 【cocos2dx中Node类getParent和getChildByTag()】学习体会

    參考http://cn.cocos2d-x.org/doc/cocos2d-x-3.0/d3/d82/classcocos2d_1_1_node.html 当中和child.parent有关的成员函数 ...

  2. Android -- Property Animation

    3.0以前,android支持两种动画模式,tween animation,frame animation,在android3.0中又引入了一个新的动画系统:property animation,这三 ...

  3. Android面试题收集

    Android是一种基于Linux的自由及开放源码的操作系统,主要使用于移动设备.如智能手机和平板电脑.由Google公司和开放手机联盟领导及开发.这里会不断收集和更新Android基础相关的面试题, ...

  4. Uniform and Interpolator Packing的作用

    All of the packing that is done is completely transparent to the user of the OpenGL ES Shading Langu ...

  5. Android Studio 上传aar(Library)到JCenter

    目的 这篇文章介绍通过Gradle把开源项目发布到公共仓库JCenter中,发布自己的android library(也就是aar)到公共的jcenter仓库. 为什么选择JCenter,因为JCen ...

  6. MongoDB数据库遭大规模勒索攻击,被劫持26000多台服务器 #精选GITHUBMYSQL

    昨天,一个大新闻爆出,MongoDB数据库叕被攻击了.就在上周末,三个黑客团伙劫持了MongoDB逾26000多台服务器,其中规模最大的一组超过22000台. “MongoDB启示录”再临?   此次 ...

  7. URAL 1224. Spiral (规律)

    1224. Spiral Time limit: 1.0 second Memory limit: 64 MB A brand new sapper robot is able to neutrali ...

  8. Linux 远程和本地的一些解决方式

     有的小伙伴想Linux 远程登录 两台机器同一时候root登录.事实上能够同一时候多个用户的. Linux是多用户的多任务系统,能够同一时候多个用户登录到系统,也能够一个用户通过不同终端登录到一个系 ...

  9. js开发思路

    $.ui = $.ui || {}; var version = $.ui.version = "1.12.1"; // 是否为ie浏览器 var ie = $.ui.ie = ! ...

  10. JavaScript String 对象扩展方法

    /** 在字符串末尾追加字符串 **/ String.prototype.append = function (str) { return this.concat(str); } /** 删除指定索引 ...