缓存

如果有很多张图片要显示,最好不要提前把所有都加载进来,而是应该当移出屏幕之后立刻销毁。通过选择性的缓存,你就可以避免来回滚动时图片重复性的加载了。

缓存其实很简单:就是存储昂贵计算后的结果(或者是从闪存或者网络加载的文件)在内存中,以便后续使用,这样访问起来很快。问题在于缓存本质上是一个权衡过程 - 为了提升性能而消耗了内存,但是由于内存是一个非常宝贵的资源,所以不能把所有东西都做缓存。

何时将何物做缓存(做多久)并不总是很明显。幸运的是,大多情况下,iOS都为我们做好了图片的缓存。

+imageNamed:方法

之前我们提到使用[UIImage imageNamed:]加载图片有个好处在于可以立刻解压图片而不用等到绘制的时候。但是[UIImage imageNamed:]方法有另一个非常显著的好处:它在内存中自动缓存了解压后的图片,即使你自己没有保留对它的任何引用。

对于iOS应用那些主要的图片(例如图标,按钮和背景图片),使用[UIImage imageNamed:]加载图片是最简单最有效的方式。在nib文件中引用的图片同样也是这个机制,所以你很多时候都在隐式的使用它。

但是[UIImage imageNamed:]并不适用任何情况。它为用户界面做了优化,但是并不是对应用程序需要显示的所有类型的图片都适用。有些时候你还是要实现自己的缓存机制,原因如下:

  • [UIImage imageNamed:]方法仅仅适用于在应用程序资源束目录下的图片,但是大多数应用的许多图片都要从网络或者是用户的相机中获取,所以[UIImage imageNamed:]就没法用了。

  • [UIImage imageNamed:]缓存用来存储应用界面的图片(按钮,背景等等)。如果对照片这种大图也用这种缓存,那么iOS系统就很可能会移除这些图片来节省内存。那么在切换页面时性能就会下降,因为这些图片都需要重新加载。对传送器的图片使用一个单独的缓存机制就可以把它和应用图片的生命周期解耦。

  • [UIImage imageNamed:]缓存机制并不是公开的,所以你不能很好地控制它。例如,你没法做到检测图片是否在加载之前就做了缓存,不能够设置缓存大小,当图片没用的时候也不能把它从缓存中移除。

自定义缓存

构建一个所谓的缓存系统非常困难。菲尔 卡尔顿曾经说过:“在计算机科学中只有两件难事:缓存和命名”。

如果要写自己的图片缓存的话,那该如何实现呢?让我们来看看要涉及哪些方面:

  • 选择一个合适的缓存键 - 缓存键用来做图片的唯一标识。如果实时创建图片,通常不太好生成一个字符串来区分别的图片。在我们的图片传送带例子中就很简单,我们可以用图片的文件名或者表格索引。

  • 提前缓存 - 如果生成和加载数据的代价很大,你可能想当第一次需要用到的时候再去加载和缓存。提前加载的逻辑是应用内在就有的,但是在我们的例子中,这也非常好实现,因为对于一个给定的位置和滚动方向,我们就可以精确地判断出哪一张图片将会出现。

  • 缓存失效 - 如果图片文件发生了变化,怎样才能通知到缓存更新呢?这是个非常困难的问题(就像菲尔 卡尔顿提到的),但是幸运的是当从程序资源加载静态图片的时候并不需要考虑这些。对用户提供的图片来说(可能会被修改或者覆盖),一个比较好的方式就是当图片缓存的时候打上一个时间戳以便当文件更新的时候作比较。

  • 缓存回收 - 当内存不够的时候,如何判断哪些缓存需要清空呢?这就需要到你写一个合适的算法了。幸运的是,对缓存回收的问题,苹果提供了一个叫做NSCache通用的解决方案

NSCache

NSCacheNSDictionary类似。你可以通过-setObject:forKey:-object:forKey:方法分别来插入,检索。和字典不同的是,NSCache在系统低内存的时候自动丢弃存储的对象。

NSCache用来判断何时丢弃对象的算法并没有在文档中给出,但是你可以使用-setCountLimit:方法设置缓存大小,以及-setObject:forKey:cost:来对每个存储的对象指定消耗的值来提供一些暗示。

指定消耗数值可以用来指定相对的重建成本。如果对大图指定一个大的消耗值,那么缓存就知道这些物体的存储更加昂贵,于是当有大的性能问题的时候才会丢弃这些物体。你也可以用-setTotalCostLimit:方法来指定全体缓存的尺寸。

NSCache是一个普遍的缓存解决方案,我们创建一个比传送器案例更好的自定义的缓存类。(例如,我们可以基于不同的缓存图片索引和当前中间索引来判断哪些图片需要首先被释放)。但是NSCache对我们当前的缓存需求来说已经足够了;没必要过早做优化。

使用图片缓存和提前加载的实现来扩展之前的传送器案例,然后来看看是否效果更好(见清单14.5)。

清单14.5 添加缓存

 #import "ViewController.h"

 @interface ViewController() 

 @property (nonatomic, copy) NSArray *imagePaths;
@property (nonatomic, weak) IBOutlet UICollectionView *collectionView; @end @implementation ViewController - (void)viewDidLoad
{
//set up data
self.imagePaths = [[NSBundle mainBundle] pathsForResourcesOfType:@"png" inDirectory:@"Vacation Photos"];
//register cell class
[self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"Cell"];
} - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [self.imagePaths count];
} - (UIImage *)loadImageAtIndex:(NSUInteger)index
{
//set up cache
static NSCache *cache = nil;
if (!cache) {
cache = [[NSCache alloc] init];
}
//if already cached, return immediately
UIImage *image = [cache objectForKey:@(index)];
if (image) {
return [image isKindOfClass:[NSNull class]]? nil: image;
}
//set placeholder to avoid reloading image multiple times
[cache setObject:[NSNull null] forKey:@(index)];
//switch to background thread
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, ), ^{
//load image
NSString *imagePath = self.imagePaths[index];
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
//redraw image using device context
UIGraphicsBeginImageContextWithOptions(image.size, YES, );
[image drawAtPoint:CGPointZero];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//set image for correct image view
dispatch_async(dispatch_get_main_queue(), ^{ //cache the image
[cache setObject:image forKey:@(index)];
//display the image
NSIndexPath *indexPath = [NSIndexPath indexPathForItem: index inSection:]; UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
UIImageView *imageView = [cell.contentView.subviews lastObject];
imageView.image = image;
});
});
//not loaded yet
return nil;
} - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
//dequeue cell
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
//add image view
UIImageView *imageView = [cell.contentView.subviews lastObject];
if (!imageView) {
imageView = [[UIImageView alloc] initWithFrame:cell.contentView.bounds];
imageView.contentMode = UIViewContentModeScaleAspectFit;
[cell.contentView addSubview:imageView];
}
//set or load image for this index
imageView.image = [self loadImageAtIndex:indexPath.item];
//preload image for previous and next index
if (indexPath.item < [self.imagePaths count] - ) {
[self loadImageAtIndex:indexPath.item + ]; }
if (indexPath.item > ) {
[self loadImageAtIndex:indexPath.item - ]; }
return cell;
} @end

果然效果更好了!当滚动的时候虽然还有一些图片进入的延迟,但是已经非常罕见了。缓存意味着我们做了更少的加载。这里提前加载逻辑非常粗暴,其实可以把滑动速度和方向也考虑进来,但这已经比之前没做缓存的版本好很多了。

缓存(图像 IO 14.2)的更多相关文章

  1. 文件格式(图像 IO 14.3)

    文件格式 图片加载性能取决于加载大图的时间和解压小图时间的权衡.很多苹果的文档都说PNG是iOS所有图片加载的最好格式.但这是极度误导的过时信息了. PNG图片使用的无损压缩算法可以比使用JPEG的图 ...

  2. CorAnimation7-高效绘图、图像IO以及图层性能

    高效绘图 软件绘图 术语绘图通常在Core Animation的上下文中指代软件绘图(意即:不由GPU协助的绘图).在iOS中,软件绘图通常是由Core Graphics框架完成来完成.但是,在一些必 ...

  3. [iOS Animation]-CALayer 图像IO

    图像IO 潜伏期值得思考 - 凯文 帕萨特 在第13章“高效绘图”中,我们研究了和Core Graphics绘图相关的性能问题,以及如何修复.和绘图性能相关紧密相关的是图像性能.在这一章中,我们将研究 ...

  4. 图像IO

    图像IO 潜伏期值得思考 - 凯文 帕萨特 在第13章“高效绘图”中,我们研究了和Core Graphics绘图相关的性能问题,以及如何修复.和绘图性能相关紧密相关的是图像性能.在这一章中,我们将研究 ...

  5. android 删除SD卡或手机的缓存图像和文件夹

    public static final String TEMP_PHOTO_FILE_NAME = "temp_photo.jpg"; private static String ...

  6. 操作系统——IO缓存技术

    一.为什么引入缓存技术 为了解决cpu速度和外部设备速度不匹配的问题. 降低了io对cpu的中断的次数.每进行一次IO设备的时间都非常长,所以把数据先放入缓冲区,再进行IO操作. 二.缓冲技术的实现 ...

  7. [Swift通天遁地]五、高级扩展-(11)图像加载Loading动画效果的自定义和缓存

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...

  8. io:轻松地创建缓存

    介绍 io模块是python中专门用来进行流处理的模块 StringIO 提供字符串形式的缓存,可以不断地往里面写入数据,最后一次性读出 import io # 创建相应的缓存 buf = io.St ...

  9. Libevent:9Evbuffers缓存IO的实用功能

    Libevent的evbuffer功能实现了一个字节队列,优化了在队列尾端增加数据,以及从队列前端删除数据的操作. Evbuffer用来实现缓存网络IO中的缓存部分.它们不能用来在条件发生时调度IO或 ...

随机推荐

  1. 《代码大全》阅读笔记-33-个人性格(personal character)

    很多好的编程做法都能减轻你的大脑灰质细胞(指脑力)的负担. 将系统"分解",是为了使之易于理解("设计的层次"). 进行审查.评审和测试正是为了减少人为失误.如 ...

  2. Linux下openoffice与SWFtools的安装

    第一部份:OpenOffice的安装 1.Openoffice的官网:http://www.openoffice.org/download/index.html 选择Linux 64-bit(x860 ...

  3. &与&&, |与||区别

    &和|称为短逻辑符,&&及||称为长逻辑符.长逻辑符只比较左边和右边的第一个元素,而短逻辑符会比较所有的 > a<-c(TRUE, FALSE, TRUE, FAL ...

  4. Python之Subprocess模块

    PS:打开文件时候加b参数是代表以二进制方式打开,在Linux加不加都可以,在windows上面最好加b参数否则可能会出现问题 使用system返回执行结果不赋值,使用popen返回了结果赋值给cmd ...

  5. POJ-1191-棋盘分割(动态规划)

    棋盘分割 Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 13593 Accepted: 4846 Description 将一个 ...

  6. codeforces 14A - Letter & codeforces 859B - Lazy Security Guard - [周赛水题]

    就像title说的,是昨天(2017/9/17)周赛的两道水题…… 题目链接:http://codeforces.com/problemset/problem/14/A time limit per ...

  7. createrepo local http yum

    https://www.jianshu.com/p/59ca879584a1   repodata作为软件的仓库,其目录下有四个必要文件:filelists.xml.[gz],other.xml.[g ...

  8. Jmeter(五)_函数

    JMeter提供了很多函数,如果能够熟练使用,可以为脚本带来很多方便. JMeter函数是一种特殊值,可用于除测试计划外的任何组件. 函数调用的格式如下所示:${__functionName(var1 ...

  9. 【mlflow】执行import mlflow 报错:ImportError: No module named 'pkg_resources'

    命令行运行 python -c “import mlflow” 的时候报错: ImportError: No module named 'pkg_resources' 结果发现是因为本地有一个文件夹叫 ...

  10. Pots--poj(bfs,输出路径)

    http://poj.org/problem?id=3414 题意: 给你两个容量为a,b的杯子:有3个操作: 1:FILL(i):把第i个杯子从水库中装满: 2:DROP(i):把第i个杯子清空: ...