你的 iOS 应用,运行速度靠谱吗?中枪的同窗莫要愁,性能优化咱有妙招。用 Xcode 自家的调试工具 Instruments,揪出那些堵线程、占内存、耗资源的问题代码,完全破掉迷局,让应用扬眉吐气!html
对于每位 iOS 开发者来讲,代码性能是个避不开的话题。随着项目的扩大和功能的增多,没通过认真调试和优化的代码,要么任性地卡顿运行,要么低调地崩溃了之……结果呢,你们用着不高兴,开发者也不开心。ios
其实要破这个局面并不难,只要在 Xcode 自带的监控调试工具 Instruments 上花点功夫,让大代码流畅运行也不是神话。Instruments 提供了不少功能,我会重点介绍一下我最经常使用的三大类:git
即便有 ARC(自动引用计数)内存管理机制,但在现实中对象之间引用复杂,循环引用致使的内存泄漏仍然难以免,因此关键时刻还要自力更生。github
针对这三方面的测试,我写了个演示应用,放在 GitHub 上,来帮助你们更直观地了解这些工具的使用方法。好,进入正题。算法
时间都去哪儿啦? Time Profiler 能够回答。它会按照设定的时间间隔(默认 1 毫秒)来跟踪每一线程的堆栈信息(stack trace),并经过比较时间间隔之间的堆栈状态,来推算出某个方法执行了多久,给出一个近似值。
在演示应用头一项「Time Profiler: System Methods」中,我用插入排序(Insertion Sort)和冒泡排序(Bubble Sort)两种算法来作性能比较,下面是 Swift 代码:swift
/* 引用自:http://waynewbishop.com/swift/sorting-algorithms/ */ func insertionSort() { var x, y, key: Int for (x = 0; x < numberList.count; x++) { key = numberList[x] for (y = x; y > -1; y--) { if key < numberList[y] { numberList.removeAtIndex(y + 1) numberList.insert(key, atIndex: y) } } } } func bubbleSort() { var x, y, z, passes, key : Int for (x = 0; x < numberList.count; ++x) { passes = (numberList.count - 1) - x; for (y = 0; y < passes; y++) { key = numberList[y] if (key > numberList[y + 1]) { z = numberList[y + 1] numberList[y + 1] = key numberList[y] = z } } } }
这段代码主要是对数组的添加和删除,两种方法执行起来耗时很少,但后台发生的系统动做却多得让人眼晕。数组
能够发现,代码用到了不少间接依赖,这些都是支撑代码运行的系统库文件。由于处理大数据集比较消耗系统资源,因此要尽量地把繁重的操做放到后台去作,上面的代码就走的后台线程。在上图的 Call Tree 中能够看到,被调用的堆栈名是 dispatch_worker_thread3。若是把它放到主线程去执行,程序确定会挂起。不信你注释掉 dispatch_async 调用看一下。缓存
再来个图片加载的例子。性能优化
这儿有三种图片加载方法:服务器
咱们来看看 Time Profiler 算出的结果是否是跟预想的同样。
进入演示应用第二项「Time Profiler: Our Methods」,点击「Reload」十次来重复加载图片,这样能产生足够的数据来分析。而后在 Time Profiler 图表中经过拖拉鼠标选中要放大查看的区域,从 Call Tree 中双击调用了 .reload 方法那一行(上图中加亮选中那一行),就会跳转到对应的代码行,所用时间也标注出来了。
看到谁最花时间了吧。虽然代码没什么可优化的地方,但你们应该认识到缓存能发挥的做用。因此即便有时还得调用 loadSlowImage,多数状况下把图片缓存下来,仍是能省些资源占用。
此外,我想再说说 Call Tree 的选项设置。
这些选项默认是不选的,但把它们勾选上能够帮你更快定位到关键的代码上,每每这也是问题的源头。
须要添加其余工具的话:
咱们常常须要从服务器下载大量图片,特别是开发照片类的应用。但每每稍不注意,内存使用就会暴增,因此得保证把这些图片缓存下来以便重复使用。下面来看看演示程序中内存分配的例子。
从图中能够看到,每次点击「Reload」从新载入图片时,内存都会出现使用峰值。应用先分配大量内存来替换原有图片,而后再释放掉这部份内存,可想而知这样的操做效率高不了,并且若是要下载更大的文件,呃,局面大概会失控吧。
看一下堆栈列表第四行,ImageIO_PNG_Data 里有 9 张处于活动状态的图片,占用了12.38 MB 内存,这些都是没被系统释放或缓存的内存,因此致使堆内存分配升高。接下来再看看使用缓存后的效果。
使用了缓存库(Swift Haneke)后,点「Reload」五次,这回在 Allocations 列表中却看不到 ImageIO_PNG_Data 对象了,这说明它是空的,没有任何图像数据。同时,All Heap Allocations 的大小已从刚才的 14.61 MB 降到了 2.51 MB。Anonymous VM(匿名虚拟内存)是系统为程序预留的、可能会当即被重复使用的一部分可用内存。要防止程序崩溃,就别让堆的尺寸增加太快。
还有就是,例子用的是异步方式来加载图片,这样用不着等到全部图片下载完才能在界面中显示。大多数图像缓存库都会把加载工做放到后台,以免延长主线程的响应周期。
尽管 Apple 推出的 ARC 能够有效防范内存泄漏,但出问题的机率仍是会有,Swift 也不例外。鉴于篇幅有限,本文就不涉及内存和 ARC 的工做原理了,具体能够参考官方文档。我会用代码来触发内存泄漏。
首先从最底层上说,当两个对象相互创建了强引用(strong reference),当一个对象被释放,另外一个对象因为是强引用的关系不容许被释放,此时 ARC 没法肯定没被释放的对象到底还有没有用,因而就致使了内存泄漏。
要解决这个问题,能够将其中的一个对象中变量设为 weak,不让它出如今保留周期中。不少开发者在管理 view controller 时常会在内存泄漏上中招,觉得换了新的 controller,老的 controller 就被释放回收了,其实还没。这样代码一多,就会形成不少对象都没被释放。因此用这个工具把整个应用跑一遍,把那些断链的强引用清理干净,会大有裨益。
除了上述这三类工具,Instruments 还有不少实用的工具,推荐你们根据本身的关注点,花些时间去学学。好比:
最后小总结下。我倒不想一味夸大 Instruments 的做用,若是应用跑得挺痛快,没出现啥调皮行为,大可把它忽略,等到问题来了再作优化。对于新手来讲,花些时间了解 Instruments 的功能,多调试多积累经验,这样作出来的应用在用户体验上确定错不了。
你最经常使用的 Instruments 工具都有哪些?欢迎与咱们分享。
原文:How To Use The 3 Instruments You Should Be Using 译者:LeanCloud