虽然 iPhone 性能越来越好,但是 App 也越来越复杂,所以性能问题也从来都是移动开发者所关心的问题之一。一个 App 性能好的表现主要是在:应用启动快速、 UI 反馈响应及时、列表滚动操作流畅、内存使用合理,更不能随随便便 Crash 。
网上有一大堆性能优化的 Tips ,宗旨是写代码之前规避一些性能问题,时常对代码进行性能方面的提升,列出一些个人碰到过的。
1 、 善用重用和延迟加载
- 像 tableView 一样,不要一下子创建所有的 subViews ,在需要的时候再创建、将 view 放到一个可重用的列表中。这样只需要在滚动发生时创建你的 views ,避免了不划算的内存分配; 重用一些创建初始化需要重大开销的对象,通过添加属性到你的 class 里或者创建静态变量来实现。
NSDateFormatter、SQLite 语句、正则表达式的初始化和设置都很昂贵。
2 、尽量把 views 设置为不透明即 opaque 属性为 YES 。
- (opaque)这个属性给渲染系统提供了一个如何处理这个 view 的提示。如果设为 YES , 渲染系统就认为这个 view 是完全不透明的,这使得渲染系统优化一些渲染过程和提高性能。如果设置为 NO ,渲染系统正常地和其它内容组成这个 View 。默认值是 YES 。
3 、避免过于复杂的 xib 文件
- 当加载一个 xib 的时候所有内容都被放在了内存里,包括任何图片。如果有一个不会即刻用到的 view ,就是在浪费宝贵的内存资源了。 Storyboards 就是另一码事儿了, storyboard 仅在需要时实例化一个
viewController.
4 、尽量避免阻塞主线程
- UIKit 在主线程上做所有工作,渲染,管理触摸反应,回应输入等都需要在它上面完成。界面卡顿就是主线程被阻塞的表现。尽量把耗时操作放到其他线程中来做,然后回到主线程来刷新界面。大部分耗时操作牵涉到读写外部资源的 I/O 操作,比如存储(读写文件)或者收发网络数据。可以使用 GCD 来进行多线程操作的,但是一个 App 的线程最好不要超过 5 条。原因
CPU 会在 N 条线程之间调度,消耗大量 CPU 资源; 每条线程被调度执行的频次会降低(线程的执行效率降低)- 为了得到更流畅的交互体验, iOS 已经将很多事情放到了其他线程中去做,比如:
View 和 layer 的动画绘制前的计算、Layer 的组合计算( drawing 后的叠加)、PNG 的解码
5 、 Cache 缓存
- 善用缓存,缓存一些经常要使用的,不易改变的。比如一些整个 App 都需要用的模型对象,或者一些计算数据。
6 、权衡渲染方法(又一个性能与空间的权衡)
- 使用事先渲染好的图片会对性能更好一点,但是图片很多会增加 bundle 的大小;但是如果在代码中不断修改某张图用作不同的用途,会给 App 增加性能负担,所以利弊就有你自己来权衡咯
7 、内存警告处理
- 如果你的 app 收到了内存警告,它就需要尽可能释放更多的内存。最佳方式是移除对缓存,图片 object 和其他一些可以重创建的 objects 的 strong references.
- UIKit 提供了几种收集低内存警告的方法: (一旦收到这类通知,你就需要释放任何不必要的内存使用)
- 在
AppDelegate中使用applicationDidReceiveMemoryWarning:的方法- 在你的自定义 UIViewController 的子类(subclass)中覆盖
didReceiveMemoryWarning- 注册并接收
UIApplicationDidReceiveMemoryWarningNotification的通知
- 例如, UIViewController 的默认行为是移除一些不可见的 view , 它的一些子类则可以补充这个方法,删掉一些额外的数据结构。一个有图片缓存的 app 可以移除不在屏幕上显示的图片。
- 例如,当整个项目的 image 都由 SDWebImage 来处理,可能 SDWebImage 越来越大,因此需要在任何一个控制器存在内存警告时,做清理操作。清理操作要在 Appdelegate 里面做:
1.取消正在下载的操作 2.清除内存缓存
本文针对用户响应和内存方面,简单介绍一下如何进行性能分析。
用户响应
用户响应即用户事件被 runloop 及时处理和响应。 runloop 其实就是 App 事件驱动的一个大循环,使程序一直运行,并接受用户输入。它有一个事件队列,决定程序在何时处理哪些事件, runloop 中通过消息队列防止出现主调方一直等待的情况。 runloop 节省 CPU 时间,没事儿的时候就睡眠,不占用 CPU 时间。有事件唤醒,就开始处理。
为了让主线程的 runloop 更好的响应用户事件,程序员就应该尽量减少主线程干重活的时间。使用 Instruments中Time Profiler工具中的Recod thread waiting选项可以统计出 app 运行时各个线程中的阻塞系统调用情况,例如文件读写 read/write ,网络读写 send/recv ,加锁 psynch_mutex_wait 等。Instruments中的System Trace工具则能够记录所有的底层系统调用。
使用Instruments的Time Profiler工具分析

Separate By Thread:线程分离,只有这样才能在调用路径中能够清晰看到占用 CPU 最大的线程.
Invert Call Tree:从上到下跟踪堆栈信息.这个选项可以快捷的看到方法调用路径最深方法占用 CPU 耗时,比如 FuncA{FunB{FunC}},勾选后堆栈以 C->B->A 把调用层级最深的 C 显示最外面.
Hide System Libraries:这个就更有用了,勾选后耗时调用路径只会显示 app 耗时的代码,性能分析普遍我们都比较关系自己代码的耗时而不是系统的.基本是必选项.注意有些代码耗时也会纳入系统层级,可以进行勾选前后前后对执行路径进行比对会非常有用.
Flattern Recursion:拼合递归。将同一递归函数产生的多条堆栈(因为递归函数会调用自己)合并为一条。
Top Functions:找到最耗时的函数或方法。
一般情况下勾选Separate By Thread和Hide System Libraries即可;然后选择一个耗时的操作,双击定位到相应的代码页。

内存
一个 app 的内存分三类:
- Leaked memory: Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument).
- Abandoned memory: Memory still referenced by your application that has no useful purpose.
- Cached memory: Memory still referenced by your application that might be used again for better performance.
其中 Leaked memory 和 Abandoned memory 都属于应该释放而没释放的内存,都是内存泄露,而 Leaks 工具只负责检测 Leaked memory,而不管 Abandoned memory。在 MRC 时代 Leaked memory 很常见,因为很容易忘了调用 release ,但在 ARC 时代更常见的内存泄露是循环引用导致的 Abandoned memory, Leaks 工具查不出这类内存泄露,应用有限。循环引用相关的问题,可以查看以前的文章。
使用Instruments的Leaks工具分析

由于 Leaks 是动态监测,所以我们需要手动操作 APP,一边操作,一边观察 Leaks 的变化,当出现红色叉时,就监测到了内存泄露,点击右上角的第二个,进行暂停检测(也可继续检测,当多个时暂停,一次处理了多个).选中有红色柱子的 Leaks ,下面有个"田"字方格,点开,选中Call Tree,在这个界面的右下角有若干选框,选中Invert Call Tree 和Hide System Libraries。选中显示的若干条中的一条,双击,会自动跳到内存泄露代码处.
推荐使用 MLeaksFinder 第三方框架来进行内存泄露的检测。 MLeaksFinder 目前能自动检测 UIViewController 和 UIView 对象的内存泄露,而且也可以扩展以检测其它类型的对象。*MLeaksFinder 的 gitHub 地址*
原理 :当一个 UIViewController 被 pop 或 dismiss 后,该 UIViewController 包括它的 view , view 的 subviews 等等将很快被释放(除非你把它设计成单例,或者持有它的强引用,但一般很少这样做)。于是,我们只需在一个 ViewController 被 pop 或 dismiss 一小段时间后,看看该 UIViewController ,它的 view , view 的 subviews 等等是否还存在,来检测内存泄露。
当发生内存泄露的时候, MLeaksFinder 会在控制台打印出哪一个 view 没有释放,导致哪一个控制器没有释放,很明显。同时也可以自己设置不监控某个特殊的控制器。

App 的性能问题主要是靠平时的注重而不是最后出问题了的检查。还可以平时配合一些小工具来进行编码。例如:内存泄露检测工具 MLeaksFinder 。
1
ichanne 2016-05-23 08:15:19 +08:00 via iPhone
写的很好,希望每个卡顿情况都有案例分析+解决方案。
|
2
misaka15 2016-05-24 12:57:22 +08:00
--.网上大部分教程都是这样的,希望加点创新的内容
|