专栏名称: Cocoa开发者社区
CocoaChina苹果开发中文社区官方微信,提供教程资源、app推广营销、招聘、外包及培训信息、各类沙龙交流活动以及更多开发者服务。
目录
相关文章推荐
51好读  ›  专栏  ›  Cocoa开发者社区

iOS性能优化:Instruments工具

Cocoa开发者社区  · 公众号  · ios  · 2016-08-26 08:03

正文

▲点击上方“ CocoaChina ”关注 即可免费学习 iOS 开发


作者: 真·袁滚滚

原文链接:https://blog.leancloud.cn/2835/

英文原文: How To Use The 3 Instruments You Should Be Using


随着项目的扩大和功能的增多,没经过认真调试和优化的代码,要么任性地卡顿运行,要么低调地崩溃了之……结果呢,大家用着不高兴,开发者也不开心。


其实要破这个局面并不难,只要在Xcode自带的监控调试工具 Instruments 上花点功夫,让大代码流畅运行也不是神话。


Instruments 提供了很多功能,我会重点介绍一下我最常用的三大类:


  • Time Profiler:分析代码的执行时间,找出导致程序变慢的原因。

  • Allocations:监测内存使用 / 分配情况。 迅速膨胀的内存可以很快让程序毙命,所以要多加防 范。

  • Leaks:找到引发内存泄漏的起点


Time Profiler


时间都去哪儿啦?Time Profiler可以回答。它会按照设定的时间间隔(默认1毫秒)来跟踪每一线程的堆栈信息(stack trace),并通过比较时间间隔之间的堆栈状态,来推算出某个方法执行了多久,给出一个近似值。


在演示应用头一项「Time Profiler: System Methods」中,我用插入排序(Insertion Sort)和冒泡排序(Bubble Sort)两种算法来做性能比较,下面是 Swift 代码:


/* 引用自:http://waynewbishop.com/swift/sorting-algorithms/ */

func insertionSort() {

var x, y, key: Int

for (x = 0; x

key = numberList[x]

for (y = x; y > -1; y--) {

if key

numberList.removeAtIndex(y + 1)

numberList.insert(key, atIndex: y)

}

}

}

}

func bubbleSort() {

var x, y, z, passes, key : Int

for (x = 0; x

passes = (numberList.count - 1) - x;

for (y = 0; y

key = numberList[y]

if (key > numberList[y + 1]) {

z = numberList[y + 1]

numberList[y + 1] = key

numberList[y] = z

}

}

}

}


这段代码主要是对数组的添加和删除,两种方法执行起来耗时不多,但后台发生的系统动作却多得让人眼晕。



可以发现,代码用到了很多间接依赖,这些都是支撑代码运行的系统库文件。因为处理大数据集比较消耗系统资源,所以要尽可能地把繁重的操作放到后台去做,上面的代码就走的后台线程。在上图的 Call Tree 中可以看到,被调用的堆栈名是 dispatchworkerthread3。如果把它放到主线程去执行,程序肯定会挂起。不信你注释掉 dispatch_async 调用看一下。


再来个图片加载的例子。



这儿有三种图片加载方法:


  • loadSlowImage1:从指定 URL 下载一张图片(加载速度慢)

  • loadImage2:从本地资源库加载一张图片(注意:没用系统缓存)

  • loadFastImage3:从系统缓存中加载一张图片(加载速度快)



看看Time Profiler算出的结果是不是跟预想的一样。


进入演示应用第二项「Time Profiler: Our Methods」,点击「Reload」十次来重复加载图片,这样能产生足够的数据来分析。然后在 Time Profiler 图表中通过拖拉鼠标选中要放大查看的区域,从 Call Tree 中双击调用了 .reload 方法那一行(上图中加亮选中那一行),就会跳转到对应的代码行,所用时间也标注出来了。



看到谁最花时间了吧。虽然代码没什么可优化的地方,但大家应该认识到缓存能发挥的作用。所以即使有时还得调用 loadSlowImage,多数情况下把图片缓存下来,还是能省些资源占用。


Call Tree 的选项设置。


这些选项默认是不选的,但把它们勾选上可以帮你更快定位到关键的代码上,往往这也是问题的源头。



  1. Separate by Thread:按线程分开做分析,这样更容易揪出那些吃资源的问题线程。特别是对于主线程,它要处理和渲染所有的接口数据,一旦受到阻塞,程序必然卡顿或停止响应。

  2. Invert Call Tree:反向输出调用树。把调用层级最深的方法显示在最上面,更容易找到最耗时的操作。

  3. Hide Missing Symbols:隐藏缺失符号。如果 dSYM 文件或其他系统架构缺失,列表中会出现很多奇怪的十六进制的数值,用此选项把这些干扰元素屏蔽掉,让列表回归清爽。

  4. Hide System Libraries:隐藏系统库文件。过滤掉各种系统调用,只显示自己的代码调用。

  5. Flattern Recursion:拼合递归。将同一递归函数产生的多条堆栈(因为递归函数会调用自己)合并为一条。

  6. Top Functions:找到最耗时的函数或方法。


需要添加其他工具的话:



Allocations


我们经常需要从服务器下载大量图片,特别是开发照片类的应用。但往往稍不注意,内存使用就会暴增,所以得保证把这些图片缓存下来以便重复使用。下面来看看演示程序中内存分配的例子。



从图中可以看到,每次点击「Reload」重新载入图片时,内存都会出现使用峰值。应用先分配大量内存来替换原有图片,然后再释放掉这部分内存,可想而知这样的操作效率高不了,而且如果要下载更大的文件,呃,局面大概会失控吧。


看一下堆栈列表第四行,ImageIOPNGData 里有 9 张处于活动状态的图片,占用了 12.38 MB 内存,这些都是没被系统释放或缓存的内存,所以导致堆内存分配升高。接下来再看看使用缓存后的效果。







请到「今天看啥」查看全文