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通常并不了 解你的自定义绘图代码,它也不能自己计算出脏区域的位置。然而,你的确可以提 供这些信息。 当你检测到指定视图或图层的指定部分需要被重绘,你直接调用 - setNeedsDisplayInRect: 来标记它,然后将影响到的矩形作为参数传入。这样就 会在一次视图刷新时调用视图的
  • -drawRect:

图层代理的
- -drawLayer:inContext:

异步绘制

UIKit的单线程天性意味着寄宿图通畅要在主线程上更新,这意味着绘制会打断用 户交互,甚至让整个app看起来处于无响应状态。
针对这个问题,有一些方法可以用到:一些情况下,我们可以推测性地提前在另 外一个线程上绘制内容,然后将由此绘出的图片直接设置为图层的内容。这实现起 来可能不是很方便,但是在特定情况下是可行的。Core Animation提供了一些选 择: CATiledLayer 和 drawsAsynchronously 属性。

图像IO

优化从闪存驱动器或者网络中加载和显示图片

加载和潜伏

  • 加载
  • 解压
线程加载
  1. 图像加载的优化 异步加载图片,避免主线程堵塞

注意事项,异步加载图片造成的问题

由于视图在UICollectionView会被循环利用,我们加载图片的时候不能确定是否被不同的索引重新复用。为了避免图片加载到错误的视图中,我们在加载前把单元格打上索引的标签,然后在设置图片的时候检测标签是否发生了改变。+

延迟解压
当加载图片的时候,iOS通常会延迟解压图片的时间,直到加载到内存之后。这就会在准备绘制图片的时候影响性能,因为需要在绘制之前进行解压

- +imageNamed: 这个方法会在加载图片后立刻解压,但是只对应用资源束中的图片有效
- +imageWithContentsOfFile:会延迟解压图片的时间,直到加载到内存之后

另一种立刻加载图片的方法就是把它设置成图层内容,或者是UIImageView的image属性。不幸的是,这又需要在主线程执行,所以不会对性能有所提升。

第三种方式就是绕过UIKit,像下面这样使用ImageIO框架:

NSInteger index = indexPath.row;
NSURL *imageURL = [NSURL fileURLWithPath:self.imagePaths[index]];
NSDictionary *options = @{(__bridge id)kCGImageSourceShouldCache: @YES}; 
CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)imageURL, NULL);
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0,(__bridge CFDictionaryRef)options);
UIImage *image = [UIImage imageWithCGImage:imageRef]; 
CGImageRelease(imageRef);
CFRelease(source);

这样就可以使用kCGImageSourceShouldCache来创建图片,强制图片立刻解压,然后在图片的生命周期保留解压后的版本。

最后一种方式就是使用UIKit加载图片,但是立刻会知道CGContext中去。图片必须要在绘制之前解压,所以就强制了解压的及时性。这样的好处在于绘制图片可以再后台线程(例如加载本身)执行,而不会阻塞UI。

有两种方式可以为强制解压提前渲染图片:

  1. 将图片的一个像素绘制成一个像素大小的CGContext。这样仍然会解压整张图片,但是绘制本身并没有消耗任何时间。这样的好处在于加载的图片并不会在特定的设备上为绘制做优化,所以可以在任何时间点绘制出来。同样iOS也就可以丢弃解压后的图片来节省内存了。
  2. 将整张图片绘制到CGContext中,丢弃原始的图片,并且用一个从上下文内容中新的图片来代替。这样比绘制单一像素那样需要更加复杂的计算,但是因此产生的图片将会为绘制做优化,而且由于原始压缩图片被抛弃了,iOS就不能够随时丢弃任何解压后的图片来节省内存了。

需要注意的是苹果特别推荐了不要使用这些诡计来绕过标准图片解压逻辑(所以也是他们选择用默认处理方式的原因),但是如果你使用很多大图来构建应用,那如果想提升性能,就只能和系统博弈了。

如果不使用+imageNamed:,那么把整张图片绘制到CGContext可能是最佳的方式了。尽管你可能认为多余的绘制相较别的解压技术而言性能不是很高,但是新创建的图片(在特定的设备上做过优化)可能比原始图片绘制的更快。


- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
                  cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    //dequeue cell
    UICollectionViewCell
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值