iOS Core Animation 性能调优 学习笔记_系统调优性能测试报告
iOS Core Animation 性能调优 学习笔记由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“系统调优性能测试报告”。
iOS Core Animation 性能调优 学习笔
记
高效绘图
一些关键词
软件绘图
上下文:指代软件绘图(意即:不由GPU协助 的绘图)软件绘图通常是由Core Graphics框架完成来完成
绘制速度 OpenGL>Core Animation>Core Graphics 消耗可观的内存
CALayer 只需要一些与自己相关 的内存:只有它的寄宿图会消耗一定的内存空间。即使直接赋给 contents 属性一 张图片,也不需要增加额外的照片存储大小。如果相同的一张图片被多个图层作 为 contents 属性,那么他们将会共用同一块内存,而不是复制内存块。
如果你实现了
-drawLayer:inContext:-drawRect:
这两个方法中的任意一个方法,图层就创建了了一个绘制上下文,这个上下文需要的大小的内存可从这个算式得出:图层宽*图 层高*4字节,宽高的单位均为像素。对于一个在Retina iPad上的全屏图层来说,这 个内存量就是 2048*1526*4字节,相当于12MB内存,图层每次重绘的时候都需要 重新抹掉内存然后重新分配。
你应该避免重绘你的视图。提高绘制性能 的秘诀就在于尽量避免去绘制。矢量图形
我们用Core Graphics来绘图的一个通常原因就是只是用图片或是图层效果不能 轻易地绘制出矢量图形。矢量绘图包含一下这些:斜线或曲线渐变 Core Animation为这些图形类型的绘制提供了专门的类,并给他们提供硬件支持(第六章『专有图层』有详细提到)。CAShapeLayer 可以绘制多边形,直线和 曲线。CATextLayer 可以绘制文本。CAGradientLayer 用来绘制渐变。这些总 体上都比Core Graphics更快,同时他们也避免了创造一个寄宿图。脏矩形,Mac OS和iOS设备将会把屏幕区分为需要重绘的区域和 不需要重绘的区域。那些需要重绘的部分被称作『脏区域』。在实际应用中,鉴于 非矩形区域边界裁剪和混合的复杂性,通常会区分出包含指定视图的矩形位置,而 这个位置就是『脏矩形』。但是Core Animation通常并不了 解你的自定义绘图代码,它也不能自己计算出脏区域的位置。然而,你的确可以提 供这些信息。当你检测到指定视图或图层的指定部分需要被重绘,你直接调用(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.imagePaths count];} imageRect.size.height)/2;
//draw tile
UIGraphicsPushContext(ctx);
[tileImage drawInRect:imageRect];
UIGraphicsPopContext();}
@end
需要解释几点:
CATiledLayer的tileSize属性单位是像素,而不是点,所以为了保证瓦片和表格尺寸一致,需要乘以屏幕比例因子。
在-drawLayer:inContext:方法中,我们需要知道图层属于哪一个indexPath以加载正确的图片。这里我们利用了CALayer的KVC来存储和检索任意的值,将图层和索引打标签。
分辨率交换
视网膜分辨率(根据苹果市场定义)代表了人的肉眼在正常视角距离能够分辨的最小像素尺寸。但是这只能应用于静态像素。当观察一个移动图片时,你的眼睛就会对细节不敏感,于是一个低分辨率的图片和视网膜质量的图片没什么区别了。+
如果需要快速加载和显示移动大图,简单的办法就是欺骗人眼,在移动传送器的时候显示一个小图(或者低分辨率),然后当停止的时候再换成大图。这意味着我们需要对每张图片存储两份不同分辨率的副本,但是幸运的是,由于需要同时支持Retina和非Retina设备,本来这就是普遍要做到的。
如果从远程源或者用户的相册加载没有可用的低分辨率版本图片,那就可以动态将大图绘制到较小的CGContext,然后存储到某处以备复用。
为了做到图片交换,我们需要利用UIScrollView的一些实现UIScrollViewDelegate协议的委托方法(和其他类似于UITableView和UICollectionView基于滚动视图的控件一样):
(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
你可以使用这几个方法来检测传送器是否停止滚动,然后加载高分辨率的图片。只要高分辨率图片和低分辨率图片尺寸颜色保持一致,你会很难察觉到替换的过程(确保在同一台机器使用相同的图像程序或者脚本生成这些图片)。
缓存 如果有很多张图片要显示,最好不要提前把所有都加载进来,而是应该当移出屏幕之后立刻销毁。通过选择性的缓存,你就可以避免来回滚动时图片重复性的加载了。
+imageNamed:方法
之前我们提到使用[UIImage imageNamed:]加载图片有个好处在于可以立刻解压图片而不用等到绘制的时候。但是[UIImage imageNamed:]方法有另一个非常显著的好处:它在内存中自动缓存了解压后的图片,即使你自己没有保留对它的任何引用。
对于iOS应用那些主要的图片(例如图标,按钮和背景图片),使用[UIImage imageNamed:]加载图片是最简单最有效的方式。在nib文件中引用的图片同样也是这个机制,所以你很多时候都在隐式的使用它。
但是[UIImage imageNamed:]并不适用任何情况。它为用户界面做了优化,但是并不是对应用程序需要显示的所有类型的图片都适用。有些时候你还是要实现自己的缓存机制,原因如下:
-[UIImage imageNamed:]方法仅仅适用于在应用程序资源束目录下的图片,但是大多数应用的许多图片都要从网络或者是用户的相机中获取,所以[UIImage imageNamed:]就没法用了。
-[UIImage imageNamed:]缓存用来存储应用界面的图片(按钮,背景等等)。如果对照片这种大图也用这种缓存,那么iOS系统就很可能会移除这些图片来节省内存。那么在切换页面时性能就会下降,因为这些图片都需要重新加载。对传送器的图片使用一个单独的缓存机制就可以把它和应用图片的生命周期解耦。
-[UIImage imageNamed:]缓存机制并不是公开的,所以你不能很好地控制它。例如,你没法做到检测图片是否在加载之前就做了缓存,不能够设置缓存大小,当图片没用的时候也不能把它从缓存中移除。
自定义缓存
构建一个所谓的缓存系统非常困难。菲尔 卡尔顿曾经说过:“在计算机科学中只有两件难事:缓存和命名”。
如果要写自己的图片缓存的话,那该如何实现呢?让我们来看看要涉及哪些方面:
-选择一个合适的缓存键如果生成和加载数据的代价很大,你可能想当第一次需要用到的时候再去加载和缓存。提前加载的逻辑是应用内在就有的,但是在我们的例子中,这也非常好实现,因为对于一个给定的位置和滚动方向,我们就可以精确地判断出哪一张图片将会出现。
-缓存失效当内存不够的时候,如何判断哪些缓存需要清空呢?这就需要到你写一个合适的算法了。幸运的是,对缓存回收的问题,苹果提供了一个叫做NSCache通用的解决方案
NSCache
NSCache和NSDictionary类似。你可以通过-setObject:forKey:和-object:forKey:方法分别来插入,检索。和字典不同的是,NSCache在系统低内存的时候自动丢弃存储的对象。
NSCache用来判断何时丢弃对象的算法并没有在文档中给出,但是你可以使用-setCountLimit:方法设置缓存大小,以及-setObject:forKey:cost:来对每个存储的对象指定消耗的值来提供一些暗示。
指定消耗数值可以用来指定相对的重建成本。如果对大图指定一个大的消耗值,那么缓存就知道这些物体的存储更加昂贵,于是当有大的性能问题的时候才会丢弃这些物体。你也可以用-setTotalCostLimit:方法来指定全体缓存的尺寸。
NSCache是一个普遍的缓存解决方案,我们创建一个比传送器案例更好的自定义的缓存类。(例如,我们可以基于不同的缓存图片索引和当前中间索引来判断哪些图片需要首先被释放)。但是NSCache对我们当前的缓存需求来说已经足够了;没必要过早做优化。
#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 cla
[self.collectionView registerCla:[UICollectionViewCell cla] forCellWithReuseIdentifier:@“Cell”];}(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
//dequeue cell
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@“Cell” forIndexPath:indexPath];
//add imnc630.comage 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
return cell;}
@end
果然效果更好了!当滚动的时候虽然还有一些图片进入的延迟,但是已经非常罕见了。缓存意味着我们做了更少的加载。这里提前加载逻辑非常粗暴,其实可以把滑动速度和方向也考虑进来,但这已经比之前没做缓存的版本好很多了。