Instruments是Xcode套件中没有被充分利用的一个工具。不少iOS开发者从没用过Instruments,或者只是用Leaks工具检测循环引用。实际上有不少Instruments工具,包括为动画性能调优的东西。objective-c
你能够经过在菜单中选择Profile选项来打开Instruments(在这以前,记住要把目标设置成iOS设备,而不是模拟器)。而后将会显示出图12.1(若是没有看到全部选项,你可能设置成了模拟器选项)。缓存
图12.1 Instruments工具选项窗口服务器
就像以前提到的那样,你应该始终将程序设置成发布选项。幸运的是,配置文件默认就是发布选项,因此你不须要在分析的时候调整编译策略。多线程
咱们将讨论以下几个工具:框架
时间分析器 - 用来测量被方法/函数打断的CPU使用状况。dom
Core Animation - 用来调试各类Core Animation性能问题。异步
OpenGL ES驱动 - 用来调试GPU性能问题。这个工具在编写Open GL代码的时候颇有用,但有时也用来处理Core Animation的工做。函数
Instruments的一个很棒的功能在于它能够建立咱们自定义的工具集。除了你初始选择的工具以外,若是在Instruments中打开Library窗口,你能够拖拽别的工具到左侧边栏。咱们将建立以上咱们提到的三个工具,而后就能够并行使用了(见图12.2)。工具
图12.2 添加额外的工具到Instruments侧边栏性能
时间分析器工具用来检测CPU的使用状况。它能够告诉咱们程序中的哪一个方法正在消耗大量的CPU时间。使用大量的CPU并不必定是个问题 - 你可能指望动画路径对CPU很是依赖,由于动画每每是iOS设备中最苛刻的任务。
可是若是你有性能问题,查看CPU时间对于判断性能是否是和CPU相关,以及定位到函数都颇有帮助(见图12.3)。
图12.3 时间分析器工具
时间分析器有一些选项来帮助咱们定位到咱们关心的的方法。可使用左侧的复选框来打开。其中最有用的是以下几点:
经过线程分离 - 这能够经过执行的线程进行分组。若是代码被多线程分离的话,那么就能够判断究竟是哪一个线程形成了问题。
隐藏系统库 - 能够隐藏全部苹果的框架代码,来帮助咱们寻找哪一段代码形成了性能瓶颈。因为咱们不能优化框架方法,因此这对定位到咱们能实际修复的代码颇有用。
只显示Obj-C代码 - 隐藏除了Objective-C以外的全部代码。大多数内部的Core Animation代码都是用C或者C++函数,因此这对咱们集中精力到咱们代码中显式调用的方法就颇有用。
Core Animation工具用来监测Core Animation性能。它给咱们提供了周期性的FPS,而且考虑到了发生在程序以外的动画(见图12.4)。
图12.4 使用可视化调试选项的Core Animation工具
Core Animation工具也提供了一系列复选框选项来帮助调试渲染瓶颈:
Color Blended Layers - 这个选项基于渲染程度对屏幕中的混合区域进行绿到红的高亮(也就是多个半透明图层的叠加)。因为重绘的缘由,混合对GPU性能会有影响,同时也是滑动或者动画帧率降低的罪魁祸首之一。
ColorHitsGreenandMissesRed - 当使用shouldRasterizep
属性的时候,耗时的图层绘制会被缓存,而后当作一个简单的扁平图片呈现。当缓存再生的时候这个选项就用红色对栅格化图层进行了高亮。若是缓存频繁再生的话,就意味着栅格化可能会有负面的性能影响了(更多关于使用shouldRasterize
的细节见第15章“图层性能”)。
Color Copied Images - 有时候寄宿图片的生成意味着Core Animation被强制生成一些图片,而后发送到渲染服务器,而不是简单的指向原始指针。这个选项把这些图片渲染成蓝色。复制图片对内存和CPU使用来讲都是一项很是昂贵的操做,因此应该尽量的避免。
Color Immediately - 一般Core Animation Instruments以每毫秒10次的频率更新图层调试颜色。对某些效果来讲,这显然太慢了。这个选项就能够用来设置每帧都更新(可能会影响到渲染性能,并且会致使帧率测量不许,因此不要一直都设置它)。
Color Misaligned Images - 这里会高亮那些被缩放或者拉伸以及没有正确对齐到像素边界的图片(也就是非整型坐标)。这些中的大多数一般都会致使图片的不正常缩放,若是把一张大图当缩略图显示,或者不正确地模糊图像,那么这个选项将会帮你识别出问题所在。
Color Offscreen-Rendered Yellow - 这里会把那些须要离屏渲染的图层高亮成黄色。这些图层极可能须要用shadowPath
或者shouldRasterize
来优化。
Color OpenGL Fast Path Blue - 这个选项会对任何直接使用OpenGL绘制的图层进行高亮。若是仅仅使用UIKit或者Core Animation的API,那么不会有任何效果。若是使用GLKView
或者CAEAGLLayer
,那若是不显示蓝色块的话就意味着你正在强制CPU渲染额外的纹理,而不是绘制到屏幕。
Flash Updated Regions - 这个选项会对重绘的内容高亮成黄色(也就是任何在软件层面使用Core Graphics绘制的图层)。这种绘图的速度很慢。若是频繁发生这种状况的话,这意味着有一个隐藏的bug或者说经过增长缓存或者使用替代方案会有提高性能的空间。
这些高亮图层的选项一样在iOS模拟器的调试菜单也可用(图12.5)。咱们以前说过用模拟器测试性能并很差,但若是你能经过这些高亮选项识别出性能问题出在什么地方的话,那么使用iOS模拟器来验证问题是否解决也是比真机测试更有效的。
图12.5 iOS模拟器中Core Animation可视化调试选项
OpenGL ES驱动工具能够帮你测量GPU的利用率,一样也是一个很好的来判断和GPU相关动画性能的指示器。它一样也提供了相似Core Animation那样显示FPS的工具(图12.6)。
图12.6 OpenGL ES驱动工具
侧栏的邮编是一系列有用的工具。其中和Core Animation性能最相关的是以下几点:
Renderer Utilization - 若是这个值超过了~50%,就意味着你的动画可能对帧率有所限制,极可能由于离屏渲染或者是重绘致使的过分混合。
Tiler Utilization - 若是这个值超过了~50%,就意味着你的动画可能限制于几何结构方面,也就是在屏幕上有太多的图层占用了。
如今咱们已经对Instruments中动画性能工具很是熟悉了,那么能够用它在现实中解决一些实际问题。
咱们建立一个简单的显示模拟联系人姓名和头像列表的应用。注意即便把头像图片存在应用本地,为了使应用看起来更真实,咱们分别实时加载图片,而不是用–imageNamed:
预加载。一样添加一些图层阴影来使得列表显示得更真实。清单12.1展现了最第一版本的实现。
清单12.1 使用假数据的一个简单联系人列表
#import "ViewController.h" #import @interface ViewController () @property (nonatomic, strong) NSArray *items; @property (nonatomic, weak) IBOutlet UITableView *tableView; @end @implementation ViewController - (NSString *)randomName { NSArray *first = @[@"Alice", @"Bob", @"Bill", @"Charles", @"Dan", @"Dave", @"Ethan", @"Frank"]; NSArray *last = @[@"Appleseed", @"Bandicoot", @"Caravan", @"Dabble", @"Ernest", @"Fortune"]; NSUInteger index1 = (rand()/(double)INT_MAX) * [first count]; NSUInteger index2 = (rand()/(double)INT_MAX) * [last count]; return [NSString stringWithFormat:@"%@ %@", first[index1], last[index2]]; } - (NSString *)randomAvatar { NSArray *images = @[@"Snowman", @"Igloo", @"Cone", @"Spaceship", @"Anchor", @"Key"]; NSUInteger index = (rand()/(double)INT_MAX) * [images count]; return images[index]; } - (void)viewDidLoad { [super viewDidLoad]; //set up data NSMutableArray *array = [NSMutableArray array]; for (int i = 0; i < 1000; i++) { //add name [array addObject:@{@"name": [self randomName], @"image": [self randomAvatar]}]; } self.items = array; //register cell class [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"Cell"]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.items count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { //dequeue cell UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; //load image NSDictionary *item = self.items[indexPath.row]; NSString *filePath = [[NSBundle mainBundle] pathForResource:item[@"image"] ofType:@"png"]; //set image and text cell.imageView.image = [UIImage imageWithContentsOfFile:filePath]; cell.textLabel.text = item[@"name"]; //set image shadow cell.imageView.layer.shadowOffset = CGSizeMake(0, 5); cell.imageView.layer.shadowOpacity = 0.75; cell.clipsToBounds = YES; //set text shadow cell.textLabel.backgroundColor = [UIColor clearColor]; cell.textLabel.layer.shadowOffset = CGSizeMake(0, 2); cell.textLabel.layer.shadowOpacity = 0.5; return cell; } @end
当快速滑动的时候就会很是卡(见图12.7的FPS计数器)。
图12.7 滑动帧率降到15FPS
仅凭直觉,咱们猜想性能瓶颈应该在图片加载。咱们实时从闪存加载图片,并且没有缓存,因此极可能是这个缘由。咱们能够用一些很赞的代码修复,而后使用GCD异步加载图片,而后缓存。。。等一下,在开始编码以前,测试一下假设是否成立。首先用咱们的三个Instruments工具分析一下程序来定位问题。咱们推测问题可能和图片加载相关,因此用Time Profiler工具来试试(图12.8)。
图12.8 用The timing profile分析联系人列表
-tableView:cellForRowAtIndexPath:
中的CPU时间总利用率只有~28%(也就是加载头像图片的地方),很是低。因而建议是CPU/IO并非真正的限制因素。而后看看是否是GPU的问题:在OpenGL ES Driver工具中检测GPU利用率(图12.9)。
图12.9 OpenGL ES Driver工具显示的GPU利用率
渲染服务利用率的值达到51%和63%。看起来GPU须要作不少工做来渲染联系人列表。
为何GPU利用率这么高呢?咱们来用Core Animation调试工具选项来检查屏幕。首先打开Color Blended Layers(图12.10)。
图12.10 使用Color Blended Layers选项调试程序
屏幕中全部红色的部分都意味着字符标签视图的高级别混合,这很正常,由于咱们把背景设置成了透明色来显示阴影效果。这就解释了为何渲染利用率这么高了。
那么离屏绘制呢?打开Core Animation工具的Color Offscreen - Rendered Yellow选项(图12.11)。
图12.11 Color Offscreen–Rendered Yellow选项
全部的表格单元内容都在离屏绘制。这必定是由于咱们给图片和标签视图添加的阴影效果。在代码中禁用阴影,而后看下性能是否有提升(图12.12)。
图12.12 禁用阴影以后运行程序接近60FPS
问题解决了。干掉阴影以后,滑动很流畅。可是咱们的联系人列表看起来没有以前好了。那如何保持阴影效果并且不会影响性能呢?
好吧,每一行的字符和头像在每一帧刷新的时候并不须要变,因此看起来UITableViewCell
的图层很是适合作缓存。咱们可使用shouldRasterize
来缓存图层内容。这将会让图层离屏以后渲染一次而后把结果保存起来,直到下次利用的时候去更新(见清单12.2)。
清单12.2 使用shouldRasterize
提升性能
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { //dequeue cell UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; ... //set text shadow cell.textLabel.backgroundColor = [UIColor clearColor]; cell.textLabel.layer.shadowOffset = CGSizeMake(0, 2); cell.textLabel.layer.shadowOpacity = 0.5; //rasterize cell.layer.shouldRasterize = YES; cell.layer.rasterizationScale = [UIScreen mainScreen].scale; return cell; }
咱们仍然离屏绘制图层内容,可是因为显式地禁用了栅格化,Core Animation就对绘图缓存告终果,因而对提升了性能。咱们能够验证缓存是否有效,在Core Animation工具中点击Color Hits Green and Misses Red选项(图12.13)。
图12.13 Color Hits Green and Misses Red验证了缓存有效
结果和预期一致 - 大部分都是绿色,只有当滑动到屏幕上的时候会闪烁成红色。所以,如今帧率更加平滑了。
因此咱们最初的设想是错的。图片的加载并非真正的瓶颈所在,并且试图把它置于一个复杂的多线程加载和缓存的实现都将是徒劳。因此在动手修复以前验证问题所在是个很好的习惯!