(1)Time Profiler:用来测量被方法/函数打断的CPU使用情况。
(2)Core Animation:用来调试各种Core Animation性能问题。
(3)OpenGL ES驱动:用来调试GPU性能问题。这个工具在编写Open GL代码的时候很有用,但有时也用来处理Core Animation的工作。
自定义的工具集 -- window -> Library
时间分析器
时间分析器工具用来检测CPU的使用情况。它可以告诉我们程序中的哪个方法正在消耗大量的CPU时间。使用大量的CPU并不一定是个问题 - 你可能期望动画路径对CPU非常依赖,因为动画往往是iOS设备中最苛刻的任务。
但是如果你有性能问题,查看CPU时间对于判断性能是不是和CPU相关,以及定位到函数都很有帮助
时间分析器有一些选项来帮助我们定位到我们关心的的方法。可以使用左侧的复选框来打开。其中最有用的是如下几点:
(1)通过线程分离 - 这可以通过执行的线程进行分组。如果代码被多线程分离的话,那么就可以判断到底是哪个线程造成了问题。
(2)隐藏系统库 - 可以隐藏所有苹果的框架代码,来帮助我们寻找哪一段代码造成了性能瓶颈。由于我们不能优化框架方法,所以这对定位到我们能实际修复的代码很有用。
(3)只显示Obj-C代码 - 隐藏除了Objective-C之外的所有代码。大多数内部的Core Animation代码都是用C或者C++函数,所以这对我们集中精力到我们代码中显式调用的方法就很有用。
Core Animation
Core Animation工具用来监测Core Animation性能。它给我们提供了周期性的FPS,并且考虑到了发生在程序之外的动画
Core Animation工具也提供了一系列复选框选项来帮助调试渲染瓶颈:
(1)Color Blended Layers- 这个选项基于渲染程度对屏幕中的混合区域进行绿到红的高亮(也就是多个半透明图层的叠加)。由于重绘的原因,混合对GPU性能会有影响,同时也是滑动或者动画帧率下降的罪魁祸首之一。
(2)ColorHitsGreenandMissesRed- 当使用shouldRasterizep属性的时候,耗时的图层绘制会被缓存,然后当做一个简单的扁平图片呈现。当缓存再生的时候这个选项就用红色对栅格化图层进行了高亮。如果缓存频繁再生的话,就意味着栅格化可能会有负面的性能影响了
(3)Color Copied Images- 有时候寄宿图片的生成意味着Core Animation被强制生成一些图片,然后发送到渲染服务器,而不是简单的指向原始指针。这个选项把这些图片渲染成蓝色。复制图片对内存和CPU使用来说都是一项非常昂贵的操作,所以应该尽可能的避免。
(4)Color Immediately- 通常Core Animation Instruments以每毫秒10次的频率更新图层调试颜色。对某些效果来说,这显然太慢了。这个选项就可以用来设置每帧都更新(可能会影响到渲染性能,而且会导致帧率测量不准,所以不要一直都设置它)。
(5)Color Misaligned Images- 这里会高亮那些被缩放或者拉伸以及没有正确对齐到像素边界的图片(也就是非整型坐标)。这些中的大多数通常都会导致图片的不正常缩放,如果把一张大图当缩略图显示,或者不正确地模糊图像,那么这个选项将会帮你识别出问题所在。
(6)Color Offscreen-Rendered Yellow- 这里会把那些需要离屏渲染的图层高亮成黄色。这些图层很可能需要用shadowPath或者shouldRasterize来优化。
(7)Color OpenGL Fast Path Blue- 这个选项会对任何直接使用OpenGL绘制的图层进行高亮。如果仅仅使用UIKit或者Core Animation的API,那么不会有任何效果。如果使用GLKView或者CAEAGLLayer,那如果不显示蓝色块的话就意味着你正在强制CPU渲染额外的纹理,而不是绘制到屏幕。
(8)Flash Updated Regions- 这个选项会对重绘的内容高亮成黄色(也就是任何在软件层面使用Core Graphics绘制的图层)。这种绘图的速度很慢。如果频繁发生这种情况的话,这意味着有一个隐藏的bug或者说通过增加缓存或者使用替代方案会有提升性能的空间。
这些高亮图层的选项同样在iOS模拟器的调试菜单也可用。我们之前说过用模拟器测试性能并不好,但如果你能通过这些高亮选项识别出性能问题出在什么地方的话,那么使用iOS模拟器来验证问题是否解决也是比真机测试更有效的。
OpenGL ES驱动
OpenGL ES驱动工具可以帮你测量GPU的利用率,同样也是一个很好的来判断和GPU相关动画性能的指示器。它同样也提供了类似Core Animation那样显示FPS的工具
其中和Core Animation性能最相关的是如下几点:
(1)Renderer Utilization- 如果这个值超过了~50%,就意味着你的动画可能对帧率有所限制,很可能因为离屏渲染或者是重绘导致的过度混合。
(2)Tiler Utilization- 如果这个值超过了~50%,就意味着你的动画可能限制于几何结构方面,也就是在屏幕上有太多的图层占用了。
每一行的字符和头像在每一帧刷新的时候并不需要变,所以看起来UITableViewCell的图层非常适合做缓存。我们可以使用shouldRasterize来缓存图层内容,这将会让图层离屏之后渲染一次然后把结果保存起来,直到下次利用的时候去更新。
高效绘图 -- 用 CA 别用 CG
软件绘图
软件绘图 == Core Graphics框架完成。在一些必要的情况下,相比Core Animation和OpenGL,Core Graphics要慢了不少。
CA:CALayer只需要一些与自己相关的内存:只有它的寄宿图会消耗一定的内存空间。即使直接赋给contents属性一张图片,也不需要增加额外的照片存储大小。如果相同的一张图片被多个图层作为contents属性,那么他们将会共用同一块内存,而不是复制内存块。
CG:CALayerDelegate协议中的-drawLayer:inContext:方法或者UIView中的-drawRect:方法
图层就创建了一个绘制上下文,这个上下文需要的大小的内存可从这个算式得出:图层宽*图层高*4字节,宽高的单位均为像素。对于一个在Retina iPad上的全屏图层来说,这个内存量就是 2048*1526*4字节,相当于12MB内存,图层每次重绘的时候都需要重新抹掉内存然后重新分配。
提高绘制性能的秘诀就在于尽量避免去绘制。
矢量图形 -- 好好玩的例子
用CG的原因:只是用图片或是图层效果不能轻易地绘制出矢量图形。
UIBezierPath 实现
Core Animation为这些图形类型的绘制提供了专门的类,并给他们提供硬件支持。CAShapeLayer可以绘制多边形,直线和曲线。CATextLayer可以绘制文本。CAGradientLayer用来绘制渐变。这些总体上都比Core Graphics更快,同时他们也避免了创造一个寄宿图。
脏矩形 -- 需要重绘的部分
模拟粉笔最简单的方法就是用一个『线刷』图片然后将它粘贴到用户手指碰触的地方,但是这个方法用CAShapeLayer没办法实现。
当一个视图被改动过了,TA可能需要重绘。但是很多情况下,只是这个视图的一部分改变了,所以重绘整个寄宿图就太浪费了。但是Core Animation通常并不了解你的自定义绘图代码,它也不能自己计算出脏区域的位置。可以提供这些信息。
当你检测到指定视图或图层的指定部分需要被重绘,你直接调用-setNeedsDisplayInRect:来标记它,然后将影响到的矩形作为参数传入。这样就会在一次视图刷新时调用视图的-drawRect:(或图层代理的-drawLayer:inContext:方法)。
传入-drawLayer:inContext:的CGContext参数会自动被裁切以适应对应的矩形。为了确定矩形的尺寸大小,你可以用CGContextGetClipBoundingBox()方法来从上下文获得大小。调用-drawRect()会更简单,因为CGRect会作为参数直接传入。
异步绘制
UIKit的单线程意味着寄宿图通常要在主线程上更新,阻塞主线程。一些情况下,我们可以推测性地提前在另外一个线程上绘制内容,然后将由此绘出的图片直接设置为图层的内容。这实现起来可能不是很方便,但是在特定情况下是可行的。Core Animation提供了一些选择:CATiledLayer和drawsAsynchronously属性。
CATiledLayer
<1> 将图层再次分割成独立更新的小块(类似于脏矩形自动更新的概念)
<2> 在多个线程中,每个小块同时调用-drawLayer:inContext:方法。这就避免了阻塞用户交互而且能够利用多核心新片来更快地绘制。只有一个小块的CATiledLayer是实现异步更新图片视图的简单方法。
drawsAsynchronously
属性对传入-drawLayer:inContext:的CGContext进行改动,允许CGContext延缓绘制命令的执行以至于不阻塞用户交互。
它自己的-drawLayer:inContext:方法只会在主线程调用,但是CGContext并不等待每个绘制命令的结束。将命令加入队列,当方法返回时,在后台线程逐个执行真正的绘制。
这个特性在需要频繁重绘的视图上效果最好(比如我们的绘图应用,或者诸如UITableViewCell之类的)
图像IO
如何优化从闪存驱动器或者网络中加载和显示图片。
加载图片
只要有可能,就应当设法在程序生命周期中不易察觉的时候加载图片,例如启动,或者在屏幕切换的过程中。按下按钮和按钮响应事件之间最大的延迟大概是200ms,远远超过动画帧切换所需要的16ms。你可以在程序首次启动的时候加载图片,但是如果20秒内无法启动程序的话,iOS检测计时器就会终止你的应用(而且如果启动时间超出2或3秒的话,用户就会抱怨)。
你不能在主线程中加载网络,并在屏幕冻结期间期望用户去等待它,所以需要后台线程。
线程加载
图片都非常小,所以可以在主线程同步加载。但是对于大图来说,这样做就不太合适了,因为加载会消耗很长时间,造成滑动的不流畅。滑动动画会在主线程的run loop中更新,它们是在渲染服务进程中运行的,并因此更容易比CAAnimation遭受CPU相关的性能问题。
这里提升性能唯一的方式就是在另一个线程中加载图片。这并不能够降低实际的加载时间(可能情况会更糟,因为系统可能要消耗CPU时间来处理加载的图片数据),但是主线程能够有时间做一些别的事情,比如响应用户输入,以及滑动动画。
为了在后台线程加载图片,我们可以使用GCD或者NSOperationQueue创建自定义线程,或者使用CATiledLayer。为了从远程网络加载图片,我们可以使用异步的NSURLConnection,但是对本地存储的图片,并不十分有效。
GCD & NSOperationQueue
同样在操作优先级和依赖关系上提供了很好的粒度控制,但是需要更多地设置代码。
需要加载图片到视图的时候切换到主线程,因为在后台线程访问视图会有安全隐患。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0), ^{ dispatch_async(dispatch_get_main_queue(), ^{
延迟解压 -- 加载 + 解码
一旦图片文件被加载就必须要进行解码
用于加载的CPU时间相对于解码来说根据图片格式而不同。对于PNG图片来说,加载会比JPEG更长,因为文件可能更大,但是解码会相对较快,而且Xcode会把PNG图片进行解码优化之后引入工程。JPEG图片更小,加载更快,但是解压的步骤要消耗更长的时间,因为JPEG解压算法比基于zip的PNG算法更加复杂。
Q:iOS图片机制 + 解决办法思路探索 => 棒!
当加载图片的时候,iOS通常会延迟解压图片的时间,直到加载到内存之后。这就会在准备绘制图片的时候影响性能,因为需要在绘制之前解压(通常是消耗时间的问题所在)。
A:
(1)最简单的方法就是使用UIImage的+imageNamed:方法避免延时加载。不像+imageWithContentsOfFile:(和其他别的UIImage加载方法),这个方法会在加载图片之后立刻进行解压。问题在于+imageNamed:只对从应用资源树中的图片有效,所以对用户生成的图片内容或者是下载的图片就没法使用了。
(2)另一种立刻加载图片的方法就是把它设置成图层内容,或者是UIImageView.image属性。不幸的是,这又需要在主线程执行,所以不会对性能有所提升。
(3)第三种方式就是绕过UIKit,使用ImageIO框架:
CGImageRef
这样就可以使用kCGImageSourceShouldCache来创建图片,强制图片立刻解压,然后在图片的生命周期保留解压后的版本。
(4)最后一种方式就是使用UIKit加载图片,但是需要立刻将它绘制到CGContext中去。图片必须要在绘制之前解压,所以就要立即强制解压。这样的好处在于绘制图片可以在后台线程(例如加载本身)中执行,而不会阻塞UI。
有两种方式可以为强制解压提前渲染图片:
(1)将图片的一个像素绘制成一个像素大小的CGContext。这样仍然会解压整张图片,但是绘制本身并没有消耗任何时间。这样的好处在于加载的图片并不会在特定的设备上为绘制做优化,所以可以在任何时间点绘制出来。同样iOS也就可以丢弃解压后的图片来节省内存了。
(2)将整张图片绘制到CGContext中,丢弃原始的图片,并且用一个从上下文内容中新的图片来代替。这样比绘制单一像素那样需要<1>更加复杂的计算,但是因此产生的图片将会为<2>绘制做优化,而且由于原始压缩图片被抛弃了,iOS就<3>不能够随时丢弃任何解压后的图片来节省内存了。
如果不使用+imageNamed:,那么把整张图片绘制到CGContext可能是最佳的方式了。尽管你可能认为多余的绘制相较别的解压技术而言性能不是很高,但是新创建的图片(在特定的设备上做过优化)可能比原始图片绘制的更快。
Note that:这个很重要啊,包括显示图片的提前裁剪
同样,如果想显示图片到比原始尺寸小的容器中,那么一次性在后台线程重新绘制到正确的尺寸会比每次显示的时候都做缩放会更有效。
dispatch_async后台队列 + UIGraphicsBeginImageContextWithOptions + dispatch_async主队列更新
CATiledLayer
CATiledLayer可以用来异步加载和显示大型图片,而不阻塞用户输入
(1)CATiledLayer的tileSize属性单位是像素,而不是点,所以为了保证瓦片和表格尺寸一致,需要乘以屏幕比例因子。
(2)在-drawLayer:inContext:方法中,我们需要知道图层属于哪一个indexPath以加载正确的图片。这里我们利用了CALayer的KVC来存储和检索任意的值,将图层和索引打标签。
在图片加载到准备绘制的时候总会有一个延迟,这将会导致滑动时候新图片的跳入。
分辨率交换 - 移动小图,停止大图。其他源在CG中绘制小图
当观察一个移动图片时,你的眼睛就会对细节不敏感,于是一个低分辨率的图片和视网膜质量的图片没什么区别了。
如果需要快速加载和显示移动大图,简单的办法就是欺骗人眼,在移动传送器的时候显示一个小图(或者低分辨率),然后当停止的时候再换成大图。这意味着我们需要对每张图片存储两份不同分辨率的副本,由于需要同时支持Retina和非Retina设备,本来就有。
如果从远程源或者用户的相册加载没有可用的低分辨率版本图片,那就可以动态将大图绘制到较小的CGContext,然后存储到某处以备复用。
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
是否停止滚动,然后加载高分辨率的图片。只要高分辨率图片和低分辨率图片尺寸颜色保持一致,你会很难察觉到替换的过程。
缓存 - 空间换时间 - 何时将何物做缓存(做多久)
通过选择性的缓存,你就可以避免来回滚动时图片重复性的加载了。从闪存或者网络加载的文件存储到内存中,以便后续使用,
(1)+imageNamed:方法 -- 网络&相机图片,大图,缓存机制控制
好处:<1> 立刻解压图片而不用等到绘制的时候。<2> 它在内存中自动缓存了解压后的图片,即使你自己没有保留对它的任何引用。
有些时候你还是要实现自己的缓存机制,原因如下:
<1> 仅仅适用于在应用程序资源束目录下的图片,但是大多数应用的许多图片都要从网络或者是用户的相机中获取。
<2> 缓存用来存储应用界面的图片(按钮,背景等等)。如果对照片这种大图也用这种缓存,那么iOS系统就很可能会移除这些图片来节省内存。那么在切换页面时性能就会下降,因为这些图片都需要重新加载。对传送器的图片使用一个单独的缓存机制就可以把它和应用图片的生命周期解耦。
<3> 缓存机制并不公开的,所以你不能很好地控制它。例如,你没法做到检测图片是否在加载之前就做了缓存,不能够设置缓存大小,当图片没用的时候也不能把它从缓存中移除。
(2)自定义缓存
<1> 选择一个合适的缓存键 - 缓存键用来做图片的唯一标识。如果实时创建图片,通常不太好生成一个字符串来区分别的图片。我们可以用图片的文件名或者表格索引。
<2> 提前缓存 - 如果生成和加载数据的代价很大,你可能想当第一次需要用到的时候再去加载和缓存。提前加载的逻辑是应用内在就有的,对于一个给定的位置和滚动方向,我们就可以精确地判断出哪一张图片将会出现。
<3> 缓存失效(时间戳) - 如果图片文件发生了变化,怎样才能通知到缓存更新呢?这是个非常困难的问题,但是幸运的是当从程序资源加载静态图片的时候并不需要考虑这些。对用户提供的图片来说(可能会被修改或者覆盖),一个比较好的方式就是当图片缓存的时候打上一个时间戳以便当文件更新的时候作比较。
<4> 缓存回收 - 当内存不够的时候,如何判断哪些缓存需要清空呢?这就需要到你写一个合适的算法了。对缓存回收的问题,苹果提供了一个叫做NSCache通用的解决方案
(3)NSCache
NSCache在系统低内存的时候自动丢弃存储的对象。
NSCache用来判断何时丢弃对象的算法并没有在文档中给出,但是你可以使用-setCountLimit:方法设置缓存大小,以及-setObject:forKey:cost:来对每个存储的对象指定消耗的值。你也可以用-setTotalCostLimit:方法来指定全体缓存的尺寸。
样例代码!!!
提前加载逻辑非常粗暴,其实可以把滑动速度和方向也考虑进来。
文件格式 -- PNG & JPEG
图片加载性能取决于加载大图的时间和解压小图时间的权衡。PNG图片使用的无损压缩算法可以比使用JPEG的图片做到更快地解压,但是由于闪存访问的原因,这些加载的时间并没有什么区别。
PNG和JPEG压缩算法作用于两种不同的图片类型:JPEG对于噪点大的图片效果很好;但是PNG更适合于扁平颜色,锋利的线条或者一些渐变色的图片。
相对于不友好的PNG图片,相同像素的JPEG图片总是比PNG加载更快,除非一些非常小的图片、但对于友好的PNG图片,一些中大尺寸的图效果还是很好的。
JPEG会是个不错的选择。如果用JPEG的话,一些多线程和缓存策略都没必要了。
但JPEG图片并不是所有情况都适用。如果图片需要一些透明效果,或者压缩之后细节损耗很多,那就该考虑用别的格式了。iOS系统中对PNG和JPEG都做了一些优化,所以普通情况下都应该用这种格式。也就是说在一些特殊的情况下才应该使用别的格式。
(2)混合图片
对于包含透明的图片来说,最好是使用压缩透明通道的PNG图片和压缩RGB部分的JPEG图片混合起来加载。这就对任何格式都适用了,而且无论从质量还是文件尺寸还是加载性能来说都和PNG和JPEG的图片相近。
CGColorSpaceRef(CGColorSpaceCreateDeviceGray) + CGImageRef(CGImageCreateCopyWithColorSpace) + CGImageCreateWithMask
(3)JPEG 2000
TIFF和GIF,JPEG 2000图片格式的支持,比JPEG质量更好,同样也对透明通道有很好的支持。加载和显示图片方面明显要比PNG和JPEG慢得多
(4)PVRTC (PowerVR Texture Compression)标准图片压缩。
PVRTC不用提前解压就可以被直接绘制到屏幕上。
一些弊端:6。使用OpehGL,PVRTC将会提供相对于别的可用格式来说非常高效的加载性能。
图层性能
隐式绘制
寄宿图可以通过<1> Core Graphics直接绘制,<2> 直接载入一个图片文件并赋值给contents属性,<3> 事先绘制一个屏幕之外的CGContext上下文
创建隐式的:<1> 使用特性的图层属性 <2> 特定的视图 <3> 特定的图层子类。
文本 -- CATextLayer & CATextLayer => CT更快,不如硬件加速 + frame要重绘
CATextLayer 和 UILabel 都是直接将文本绘制在图层的寄宿图中。事实上这两种方式用了完全不同的渲染方式:在iOS 6及之前,UILabel = WebKit的HTML渲染引擎来绘制文本,CATextLayer用的是Core Text。CT渲染更迅速,所有需要绘制大量文本的情形下都优先使用它。都用了软件绘制,比硬件加速合成方式要慢。
尽可能地避免改变那些包含文本视图.frame,因为这样做的话文本就需要重绘。EX:如果你想在图层的角落里显示一段静态的文本,但是这个图层经常改动,你就应该把文本放在一个子图层中。
光栅化(Rasterize) -- 缓存绘制到contents和子图层,多子图层&复杂效果
<1> 可以解决重叠透明图层的混合失灵问题 (因透明度不同,叠在一起不能保持相同的透明度)。<2> 作为绘制复杂图层树结构的优化方法。
启用shouldRasterize属性会将图层绘制到一个屏幕之外的图像。然后这个图像将会被缓存起来并绘制到实际图层的contents和子图层。如果有很多的子图层或者有复杂的效果应用,这样做就会比重绘所有事务的所有帧好(光栅化原始图像需要时间,消耗额外的内存)。
note that:一定要避免作用在内容不断变动的图层上,否则它缓存方面的优势失灵。
Instrument => Color Hits Green和Misses Red
离屏渲染
图层的以下属性将会触发屏幕外绘制:
<1> 圆角(与maskToBounds一起)
<2> 图层蒙板(mask)
<3> 阴影(shadow)
可以把那些需要屏幕外绘制的图层开启光栅化以作为一个优化方式,前提是图层并不会被频繁地重绘。
需要动画 + 屏幕外渲染的图层来说,你可以用CAShapeLayer,contentsCenter或者shadowPath来获得同样的表现。
(1)CAShapeLayer -- 圆角+矩形边裁切 = UIBezierPath + CA
cornerRadius和maskToBounds独立不会有太大的性能问题,结合在一起,就触发了屏幕外渲染。
只是圆角且沿着矩形边界裁切,可以用现成的UIBezierPath的构造器+bezierPathWithRoundedRect:cornerRadius:,不会比cornerRadius更快,但避免了性能问题。
(2)可伸缩图片 -- 图片 + contentsCenter
另一个创建圆角矩形的方法就是用一个圆形内容图片并结合contentsCenter属性去创建一个可伸缩图片。理论上来说,这个应该比用CAShapeLayer要快。
优势:可以绘制成任意边框效果而不需要额外的性能消耗。可伸缩图片甚至还可以显示出矩形阴影的效果。
(3)shadowPath -- 属性 + 阴影背景图片
如果图层是一个简单几何图形=矩形,圆角矩形(假设不包含任何透明部分或者子图层),创建出一个对应形状的阴影路径就比较容易,CA绘制简单,避免了屏幕外的图层部分的预排版需求。这对性能来说很有帮助。
图层是更复杂的图形,生成正确的阴影路径比较难,考虑用绘图软件预先生成一个阴影背景图。
这个属性非空时,定义了用于构建图层阴影的轮廓,而不是用于图层合成的alpha通道。这条path用非0 winding规则渲染。用这个属性 ->明确的指定这条路径,通常将提升渲染性能,在多图层之间共享相同路径的引用。
混合和过度绘制 -- 不透明opaque + 光栅化成单张图片
GPU每一帧可以绘制的最大像素数量(fill rate),GPU会放弃绘制那些被其他图层完全遮挡的像素,合并不同图层的透明重叠像素(即混合)消耗的资源也是相当客观的。所以为了加速处理进程,一般不要使用透明图层。你应该这样做:
(1)给视图的backgroundColor = 一个固定的,不透明的颜色
(2)设置 opaque = YES
使用shouldRasterize属性,可以将一个固定的图层体系折叠成单张图片,这样就不需要每一帧重新合成了,也就不会有因为子图层之间的混合和过度绘制的性能问题了。
减少图层数量
初始化图层,处理图层,打包通过IPC发给渲染引擎,转化成OpenGL几何图形,这些是一个图层的大致资源开销。
裁切 -- 动态实例化 + 可视区域的裁切
图层不可见:
<1> 图层在屏幕边界之外,或是在父图层边界之外。
<2> 完全在一个不透明图层之后。
<3> 完全透明
随着视图的滚动动态地实例化图层而不是事先都分配好。
对象回收 -- 重用机制 = 维护池 + 结束添加 + 开始取出 + 空创建(禁止隐式动画)
处理巨大数量的相似视图或图层时还有一个技巧就是回收他们。Table和Collection都有用到,MKMapView中的动画pin码也有用到。
对象回收的基础原则就是你需要创建一个相似对象池。当一个对象的指定实例结束了使命,你把它添加到对象池中。每次当你需要一个实例时,你就从池中取出一个。当且仅当池中为空时再创建一个新的。
避免了不断创建和释放对象(相当消耗资源,因为涉及到内存的分配和销毁)而且也不必给相似实例重复赋值。
UIKit有时候用一个标识符字符串来区分存储在不同对象池中的不同的可回收对象类型。
当设置图层属性时我们用了一个CATransaction来抑制动画效果。在之前并不需要这样做,因为在显示之前我们给所有图层设置一次属性。但是既然图层正在被回收,禁止隐式动画就有必要了,不然当属性值改变时,图层的隐式动画就会被触发。
Core Graphics绘制
你可以把他们全部替换成一个单独的视图,然后用-drawRect:方法绘制出那些复杂的视图层级。
-renderInContext: 方法 -- 就是从上下文中拿render的图片
使用CALayer的-renderInContext:方法,你可以将图层及其子图层snapshot进一个Core Graphics上下文然后得到一个图片,<1> 直接显示在UIImageView中,<2> 作为另一个图层的contents。(不同于shouldRasterize—— 要求图层与图层树相关联 )这个方法没有持续的性能消耗。
当图层内容改变时,刷新这张图片的机会取决于你(shouldRasterize,它自动地处理缓存和缓存验证),但是一旦图片被生成,相比CA处理一个复杂的图层树,你节省了相当客观的性能。
CAShapeLayer -- 只是.path,主要靠UIBezierPath
CAShapeLayer是一个通过矢量图形而不是bitmap来绘制的图层子类。你指定诸如颜色和线宽等属性,用CGPath来定义想要绘制的图形,最后CAShapeLayer就自动渲染出来了。可以用Core Graphics直接向原始的CALyer的内容中绘制一个路径,相比直下,CAShapeLayer优点:
(1)渲染快速。CAShapeLayer使用了硬件加速,绘制同一图形会比用CG快很多。
(2)高效使用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。
(3)不会被图层边界剪裁掉。一个CAShapeLayer可以在边界之外绘制。你的图层路径不会像在使用CG的普通CALayer一样被剪裁掉。
(4)不会出现像素化。当你给CAShapeLayer做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化
创建一个CGPath
CAShapeLayer可以用来绘制所有能够通过CGPath来表示的形状。属性:lineWith(线宽,用点表示单位),lineCap(线条结尾的样子),和lineJoin(线条之间的结合点的样子);
圆角
虽然使用CAShapeLayer类优势就是可以单独指定每个角。
[UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii];
CATextLayer -- 颜色,字体,对齐
UILabel的精髓:如果你想在一个图层里面显示文字,完全可以借助图层代理直接将字符串使用CG写入图层的内容。
CATextLayer以图层的形式包含了UILabel几乎所有的绘制特性,并且额外提供了一些新的特性。CATextLayer也要比UILabel渲染得快得多。
Tips:iOS 6及之前的版本,UILabel通过WebKit来实现绘制的,当有很多文字的时会有极大的性能压力。而CATextLayer使用了Core text,并且渲染得非常快。
文本像素化。没有以Retina的方式渲染,contentScale(用来决定图层内容应该以怎样的分辨率来渲染不关心屏幕的拉伸因素而总是默认为1.0)。以Retina的质量来显示文字。
textLayer.contentsScale = [UIScreen mainScreen].scale;
富文本
如果你想要支持更低版本的iOS系统,CATextLayer无疑是你向界面中增加富文本的好办法
UILabel的替代品 -- +layerClass
一个用CATextLayer作为宿主图层的UILabel子类,这样就可以随着视图自动调整大小而且也没有冗余的寄宿图啦。
每一个UIView都是寄宿在一个CALayer的示例上。layer是由视图自动创建和管理的,继承了UIView,可以重写+layerClass方法使得在创建的时候能返回一个不同的图层子类。UIView会在初始化的时候调用+layerClass方法,然后用它的返回类型来创建宿主图层。
把CATextLayer作为宿主图层的另一好处就是视图自动设置contentsScale属性。
CATransformLayer
因为它不能显示它自己的内容。只有当存在了一个能作用于子图层的变换它才真正存在。CATransformLayer并不平面化它的子图层,所以它能够用于构造一个层级的3D结构。
CALayer <-- CATransformLayer <-- CALayer(frame,backgroundColor,CATransform3D) + CATransform3D + position
CAGradientLayer
CAGradientLayer是用来生成两种或更多颜色平滑渐变的。用CG复制一个CAGradientLayer并将内容绘制到一个普通图层的寄宿图,真正好处在于绘制使用了硬件加速。
基础渐变
colors属性,startPoint和endPoint属性,他们决定了渐变的方向,单位坐标系进行的定义。
多重渐变
用locations属性来调整空间。locations属性是一个浮点数值的数组(以NSNumber包装)。这些浮点数定义了colors属性中每个不同颜色的位置,同样的,也是以单位坐标系进行标定。0.0代表着渐变的开始,1.0代表着结束。
CAReplicatorLayer
为了高效生成许多相似的图层。它会绘制一个或多个图层的子图层,并在每个复制体上应用不同的变换。
instanceCount属性指定了图层需要重复多少次。instanceTransform指定了一个CATransform3D -- 3D变换(这种情况下,下一图层的位移和旋转将会移动到圆圈的下一个点)。
反射
transform =CATransform3DScale(transform,1, -1,0);
CAScrollLayer
CAScrollLayer有一个-scrollToPoint:方法,它自动适应bounds的原点以便图层内容出现在滑动的地方。注意,这就是它做的所有事情。Core Animation并不处理用户输入,所以CAScrollLayer并不负责将触摸事件转换为滑动事件,既不渲染滚动条,也不实现任何iOS指定行为例如滑动反弹。
不同于UIScrollView,我们定制的滑动视图类并没有实现任何形式的边界检查(bounds checking)。滑出视图的边界并无限滑下去。CAScrollLayer并没有等同于UIScrollView中contentSize的属性,滑动的时候完全没有一个全局的可滑动区域的概念,也无法自适应它的边界原点至你指定的值。因为它不需要,内容完全可以超过边界。
scrollPoint:方法从图层树中查找并找到第一个可用的CAScrollLayer,然后滑动它使得指定点成为可视的。scrollRectToVisible:方法实现了同样的事情只不过是作用在一个矩形上的。visibleRect属性决定图层(如果存在的话)的哪部分是当前的可视区域。
CATiledLayer
绘制在iOS上的图片也有一个大小限制。所有显示在屏幕上的图片最终都会被转化为OpenGL纹理,同时OpenGL有一个最大的纹理尺寸(2048*2048,或4096*4096)。如果你想在单个纹理中显示一个比这大的图,即便图片已经存在于内存中了,很大的性能问题,因为Core Animation强制用CPU处理图片而不是更快的GPU。
CATiledLayer为载入大图造成的性能问题提供了一个解决方案:将大图分解成小片然后将他们单独按需载入。
小片裁剪 -- 裁剪 + drawInRect:
256*256是CATiledLayer的默认小图大小,默认大小可以通过tileSize(以像素为单位)属性更改
fadeDuration属性改变淡入时长或直接禁用掉。CATiledLayer支持多线程绘制,-drawLayer:inContext:方法可以在多个线程中同时地并发调用,所以请小心谨慎地确保你在这个方法中实现的绘制代码是线程安全的。
CAEmitterLayer
CAEmitterLayer是一个高性能的粒子引擎,被用来创建实时例子动画如:烟雾,火,雨等等这些效果。
CAEmitterLayer看上去像是许多CAEmitterCell的容器,这些CAEmitierCell定义了一个例子效果。你将会为不同的例子效果定义一个或多个CAEmitterCell作为模版,同时CAEmitterLayer负责基于这些模版实例化一个粒子流。
CAEMitterCell的属性基本上可以分为三种:
(1)这种粒子的某一属性的初始值。比如,color属性指定了一个可以混合图片内容颜色的混合色。在示例中,我们将它设置为桔色。
(2)粒子某一属性的变化范围。比如emissionRange属性的值是2π,这意味着粒子可以从360度任意位置反射出来。如果指定一个小一些的值,就可以创造出一个圆锥形。
(3)指定值在时间线上的变化。比如,在示例中,我们将alphaSpeed设置为-0.4,就是说粒子的透明度每过一秒就是减少0.4,这样就有发射出去之后逐渐消失的效果。
CAEmitterLayer的属性它自己控制着整个粒子系统的位置和形状。一些属性比如birthRate,lifetime和celocity,这些属性在CAEmitterCell中也有。这些属性会以相乘的方式作用在一起,这样你就可以用一个值来加速或者扩大整个粒子系统。
(1)preservesDepth,是否将3D粒子系统平面化到一个图层(默认值)或者可以在3D空间中混合其他的图层。
(2)renderMode,控制着在视觉上粒子图片是如何混合的。你可能已经注意到了示例中我们把它设置为kCAEmitterLayerAdditive:合并粒子重叠部分的亮度使得看上去更亮。
CAEAGLLayer
当iOS要处理高性能图形绘制,必要时就是OpenGL。
在iOS 5中,苹果引入了一个新的框架叫做GLKit,它去掉了一些设置OpenGL的复杂性,提供了一个叫做CLKView的UIView的子类,帮你处理大部分的设置和绘制工作。前提是各种各样的OpenGL绘图缓冲的底层可配置项仍然需要你用CAEAGLLayer完成,它是CALayer的一个子类,用来显示任意的OpenGL图形。
AVPlayerLayer
AVPlayerLayer是有别的框架(AVFoundation)提供的,它和Core Animation紧密地结合在一起,提供了一个CALayer子类来显示自定义的内容类型。
AVPlayerLayer是用来在iOS上播放视频的。他是高级接口例如MPMoivePlayer的底层实现,提供了显示视频的底层控制。AVPlayerLayer的使用相当简单:你可以用+playerLayerWithPlayer:方法创建一个已经绑定了视频播放器的图层,或者你可以先创建一个图层,然后用player属性绑定一个AVPlayer实例。
Core Animation并不支持自动大小和自动布局