Optimize App Performance

之前写过一篇关于性能的文章,最近又需要优化性能,所以在此基础上写一篇最近的优化总结

性能优化的几个方面:内存问题、电量消耗、启动时间、执行与响应速度、网络条件等等,下面就从这几个方面讨论下如何优化

内存问题

可借助工具:Instruments -> Leaks / Allocations / Zombies

内存问题,主要包括避免内存泄漏(使用 ARC,Block 中使用变量不要循环引用,可以借鉴以前的文章)、及时释放可以释放的无用内存(例如在一个循环中处理图片,可以使用自动释放池)

电量消耗

可借助工具:Instruments -> Energy Log

当 CPU 占用率较高,使用网络 / GPS 等情况下,电量会消耗较快。

降低 CPU 使用率的方式是优化代码中的算法,能使用系统 API 的尽量使用系统 API,这要求我们对不常用的 API 要有所了解,另外多看别人的源代码,对于不了解的 API 要查询下用法,算法方面,除了好脑子就要靠平时多看算法类的书籍锻炼了。

网络问题见下详述

GPS检查,设置合适的处理精度和距离,使用合适的 API 来更新位置。

例如在非实时(追踪走路等)定位的场景下,调用如下方法延时获取数据

[locationManager allowDeferredLocationUpdatesUntilTraveled:timeout:]

或者位置有重大变化(天气应用)时才需要获取数据,调用如下方法

[locationManager startMonitoringSignificantLocationChanges]

再或者使用区域检测

[locationManager startMonitoringForRegion:]

启动时间

另一篇文章

执行与响应速度

可借助工具:Instruments -> Time Profiler

通过 Time Profiler 可以找到调用时间较长的 API,对消耗时间较长的代码进行优化,找到可优化代码只是第一步,比较困难的可能是如何优化,下面就几个方面讨论下优化途径

缓存

常用的小图片可以用内存的方式加载 [UIImage imageName:] 方法

列表滚动需要缓存高度

部分常用的数据也可以放到缓存中,需要考虑何时更新缓存以及删除缓存的时机

多线程

非 UI 操作是否可以放到线程中,例如大图片的解压,文件的保存( GCD 创建 dispatch_queue 指定优先级的时候,有一个 DISPATCH_QUEUE_PRIORITY_BACKGROUND 级别,适合写文件),数据的处理等。另外多线程中处理的数据最好是 immutable 不可变的。

另外创建线程也是有开销的,成本主要是构造内核数据结构( 1KB 左右),栈空间(主线程 1M 左右,子线程 512K),创建线程大约需要消耗 90 毫秒

代码优化

优化过程中如果发现相同代码被复制粘贴过多次的话,一定顺手将其改掉,合并为一处代码

是否有不必要的循环语句在执行,是否有对象重复创建其实使用一个创建的对象即可;如果循环中查找到结果是否在必要的地方写了 break;

GPU 离屏渲染等优化

可借助工具:Instruments -> Core Animation

另一篇文章

数据库查询优化

如果使用了数据库,那么数据库的操作是一个很容易导致响应时间问题的罪魁祸首

看下经常查找的字段上是否建立了索引,注意的是建立索引的字段上是否使用了 BETWEEN OR LIKE NULL 等会导致全文查找的条件,索引失效

WHERE 条件,过滤大数据的条件应写到最后
由于 SQL WHERE 分析和执行顺序是从右到左的,所以可以某个条件可以过滤掉大部分数据的话,这样的条件应该写在最后
例如 SELECT * FROM TABLE Where a = ‘1’ and b = ‘1’, 表中 b 列值为 1的数据比较少时 应当 b 写到最后,因为先执行 b = ‘1’ 的判断

LIKE 可以使用 MATCH 替换
http://www.jianshu.com/p/854f0d3fa240

另外要查找是否有地方使用了 SQL 查找和内存查找并存,但是可以都使用 SQL 替代实现的代码

最后,应当对 SQL 执行时间超过 5ms 的操作予以记录,我们现在使用的日志库是 CocoaLumberjack,比较好用,并在这个库的基础上做了一些调整,专门用于记录需要优化的部分执行时间。

我写了一个调试工具,目前该工具含有以下功能

  • 专门将 CocoaLumberjack 中日志级别为警告和错误的信息显示到窗口上,可以随时知道哪里出错了
  • 将 addObserver 和 removeObserver 所有调用的打印,可以人工方式排查注册过但是没有被移除的通知
  • 为了解决接管他人项目找不到页面对应类的问题,而实现的显示当前 ViewController 名称到顶层窗口。

对这个库感兴趣的话,请点击这里

其他

对常用的数据合理的使用缓存

使用懒加载的方式处理非必要存在的变量,例如某个 View,只有在某个操作下才会显示,可以使用懒加载的方式创建

是否有不必要的读写文件过程,可以放到内存中即可。如果需要读写文件,确认是否可以使用线程,是否可以使用缓存,是否可以将多次写入合并写入。

重大开销对象,例如 NSDateFormatter和 NSCalendar,如果频繁使用,可以创建单例

使用更优的 API 和合适的数据类型,例如无序且没有重复数据的数组可以替换为集合 NSSet,集合比数组在检索上效率更高,由于集合上的数值都有 hash 值,检索时都会操作哈希表来进行优化,所以相对于 NSArray 中的 containsObject、indexOfObject、removeObject 等复杂度大于等于 N 的操作,使用 NSSet 其时间复杂度都为 1

如有有些操作确实比较耗时,又无法优化,可以和 UI UE 商量下如何制作出一个动效,好的动效可以让用户察觉不到相应速度的问题

网络

可借助工具:Instruments -> Network

请求网络时判断当前的网络状态,不同网络状态下获取不同数据,例如图片大小要不一样等。如果请求失败,可以记录下失败的请求地址,再尝试下载的时候应间隔时间长一些

另外,手机 “设置 -> 开发者 -> NetWork Link Conditioner” 可以模拟网络环境很差的情况,便于调试。

请我喝汽水儿