缓存(图像 IO 14.2)

本文介绍iOS应用中图片缓存的重要性,并讨论了如何利用NSCache进行有效的内存管理。文章详细阐述了缓存机制的选择、提前加载逻辑、缓存失效处理及缓存回收策略。

缓存

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

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

何时将何物做缓存(做多久)并不总是很明显。幸运的是,大多情况下,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 添加缓存

 1 #import "ViewController.h"
 2 
 3 @interface ViewController() 
 4 
 5 @property (nonatomic, copy) NSArray *imagePaths;
 6 @property (nonatomic, weak) IBOutlet UICollectionView *collectionView;
 7 
 8 @end
 9 
10 @implementation ViewController
11 
12 - (void)viewDidLoad
13 {
14     //set up data
15     self.imagePaths = [[NSBundle mainBundle] pathsForResourcesOfType:@"png" inDirectory:@"Vacation Photos"];
16     //register cell class
17     [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"Cell"];
18 }
19 
20 - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
21 {
22     return [self.imagePaths count];
23 }
24 
25 - (UIImage *)loadImageAtIndex:(NSUInteger)index
26 {
27     //set up cache
28     static NSCache *cache = nil;
29     if (!cache) {
30         cache = [[NSCache alloc] init];
31     }
32     //if already cached, return immediately
33     UIImage *image = [cache objectForKey:@(index)];
34     if (image) {
35         return [image isKindOfClass:[NSNull class]]? nil: image;
36     }
37     //set placeholder to avoid reloading image multiple times
38     [cache setObject:[NSNull null] forKey:@(index)];
39     //switch to background thread
40     dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
41         //load image
42         NSString *imagePath = self.imagePaths[index];
43         UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
44         //redraw image using device context
45         UIGraphicsBeginImageContextWithOptions(image.size, YES, 0);
46         [image drawAtPoint:CGPointZero];
47         image = UIGraphicsGetImageFromCurrentImageContext();
48         UIGraphicsEndImageContext();
49         //set image for correct image view
50         dispatch_async(dispatch_get_main_queue(), ^{ //cache the image
51             [cache setObject:image forKey:@(index)];
52             //display the image
53             NSIndexPath *indexPath = [NSIndexPath indexPathForItem: index inSection:0]; UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
54             UIImageView *imageView = [cell.contentView.subviews lastObject];
55             imageView.image = image;
56         });
57     });
58     //not loaded yet
59     return nil;
60 }
61 
62 - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
63 {
64     //dequeue cell
65     UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
66     //add image view
67     UIImageView *imageView = [cell.contentView.subviews lastObject];
68     if (!imageView) {
69         imageView = [[UIImageView alloc] initWithFrame:cell.contentView.bounds];
70         imageView.contentMode = UIViewContentModeScaleAspectFit;
71         [cell.contentView addSubview:imageView];
72     }
73     //set or load image for this index
74     imageView.image = [self loadImageAtIndex:indexPath.item];
75     //preload image for previous and next index
76     if (indexPath.item < [self.imagePaths count] - 1) {
77         [self loadImageAtIndex:indexPath.item + 1]; }
78     if (indexPath.item > 0) {
79         [self loadImageAtIndex:indexPath.item - 1]; }
80     return cell;
81 }
82 
83 @end
View Code

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

转载于:https://www.cnblogs.com/EchoHG/p/7630274.html

在“天池平台二手车交易价值评估竞赛”这一数据科学任务中,参与者需构建预测模型以估算二手车辆的市场成交价。此类赛事属于机器学习与数据分析领域的典型应用场景,旨在系统提升参赛者的特征构建、模型优化及结果验证能力。下文将分模块阐述关键技术要点: 1. 数据清洗与规整 原始数据集需经过系统处理,包括填补空缺数值、识别离群观测、剔除冗余字段,并将分类变量编码为模型可读的数值形式。此阶段质量直接影响后续建模效果。 2. 特征构建与筛选 需从原始字段中提炼有效预测因子,包括但不限于车辆制造厂商、出厂年份、行驶总里程、外观配色及动力系统配置。基于领域常识可衍生新特征,例如车龄换算、年均行驶强度指数等。 3. 数据分布探查 通过统计图表分析变量间关联规律,例如采用趋势线观察里程数与价格的相关性,使用分位数图示不同品牌的价格区间分布特征。 4. 算法模型选型 常用预测架构包括线性回归模型、树型决策结构、集成学习方法(如随机森林、XGBoost、LightGBM)、支持向量机及深度学习网络。需根据数据特性与计算资源进行综合选择。 5. 参数优化流程 采用K折交叉验证评估模型稳定性,配合网格搜索或随机搜索策略进行超参数调优,以最大化模型预测精度。 6. 集成策略应用 通过Bagging、Boosting或堆叠融合等技术整合多个基模型,通常能获得超越单一模型的表现。 7. 性能度量标准 预测任务常用评估指标包括均方误差、平均绝对误差、均方根误差及决定系数。不同业务场景需针对性选择评估体系。 8. 结果输出规范 最终预测结果需按赛事要求整理为特定结构的数据文件,通常包含样本标识符与对应价格预测值两列。 9. 时序特征处理 当数据包含交易时间维度时,需引入时间序列分析方法(如季节性分解、循环神经网络)捕捉市场波动规律。 10. 工程实践规范 采用Git进行版本追踪,通过模块化编程提升代码可维护性,建立标准化实验记录体系。 该竞赛全面覆盖数据预处理、特征工程、模型构建与验证等核心环节,同时强调工程化实施规范,既能强化技术理论认知,又可培养实际业务场景的问题解决能力。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值