【iOS 印象】性能优化梳理(Swift)

性能监控

  • 业务性能监控:在 App 中业务的开始与结束打点上报,以达到后台统计监控性能;
  • 卡顿监控:
    • 主线程卡顿监控,经过子线程监测主线程的 runLoop,判断两个区域状态之间的耗时是否达到必定阈值。
    • FPS监控。要保持流畅的UI交互,App 刷新率应该当努力保持在 60fps。监控实现原理比较简单,经过记录两次刷新时间间隔,就能够计算出当前的 FPS。

内存分配与释放

  • 基于栈(stack-based)的内存分配

栈是一种很是简单的数据结构; 数据从栈的顶部推入(push)与弹出(pop); 因为只可以修改栈的末端,所以能够经过维护一个指向栈末端的指针来实现这种数据结构;ios

  • 基于堆(heap-based)的内存分配

内存分配具有更加动态的生命周期,更复杂的数据结构 在堆上进行内存分配,须要为对象开辟并锁定堆当中的一个空闲块(free block) 为了线程安全,必须进行锁定和同步git

引用计数

引用计数(reference counting)操做自己相对不耗费性能,但因为使用次数足够多,所以它带来的性能影响比较大。引用计数是 Objective-C 和 Swift 中用于肯定什么时候该释放对象的安全机制。Swift 当中的引用计数是强制自动管理(ARC, Auto Reference Counting),所以容易被开发者所忽略。github

调度

Swift 中有三种类型的调度(Dispatch)方式:数据库

  • Swift 会尽量将函数内联(inline),这样的话,该函数就能够直接调用,不会有额外的性能开销。
  • 静态调度(static dispatch)本质是经过 V-table 进行的查找和跳转,这个操做消耗大概 1nm。
  • 动态调度(dynamic dispatch)消耗大概 5nm。只有几个方法进行这样的动态调度的话,问题不大。应该避免在嵌套循环或执行次数较多的操做中采用动态调度的方式。

V-table: virtual table,虚函数表,记录了类中全部虚函数的函数指针,也就是说是个函数指针数组的起始位置。swift

对象

Swift 中有两种类型的对象:数组

  • 类(Class)
class Index {
	let section: Int
	let item: Int
}

let i = Index(section: 1, item: 2)
let i2 = i
复制代码

类当中的数据是在堆上分配内存。 Index 这个类包含了两个属性,sectionitem,当该对象被建立时,堆上便开辟了sectionitem 的数据空间,并生成一个指向该对象的指针 i。若是对其添加一个引用,则 i2 指向堆上相同区域,这两个对象指针之间是共用同一块内存空间的,同时会对该对象自动插入持有操做,引用计数+1。缓存

  • 结构体(Struct)
struct Index {
	let section: Int
	let item: Int
}

let i = Index(section: 1, item: 1)
let i2 = i
复制代码

一般咱们会说,要编写性能优异的 Swift 代码,最简单的方式尽量使用结构体。安全

结构体储存在栈上,基于栈进行内存分配,而且一般使用静态调度或内联调度。若是将其赋值给另外一个变量 i2,这会将储存在栈上的值复制一遍产生新的对象,而不是引用。性能优化

另:。枚举也是值类型,采用枚举改进数据模型,避免使用大量的字符串。微信

抽象类型,协议会致使性能降低

协议内部会有储存值的缓存区、元数据,而且要支持动态调度派发,有一个协议记录表(protocol witness table,也称做虚函数表),所以协议类型所占内存要比具体的类、结构体或枚举要更大。这个能够简单了解一下,以后考虑另起一篇文章单独记录下这个问题。“过早的优化是万恶之源”,相比面向协议带来的好处,通常来讲能够不用太过考虑使用协议带来的细微性能消耗。

即使如此,在面向协议开发的过程当中,也能够适当地注意如下几点,在利用协议带来的好处的同时,避免协议带来的性能损耗:

  • 若某种协议只用于类,可添加: class做为约束,采用类协议,如代理(delegate)协议的使用。
  • 将协议做为泛型约束来使用,而不是单独做为类型参数,这样编译器能够对其进行优化。

GCD 进行多线程性能优化

经过 GCD 将一些耗时操做派发到非主线程,提升 UI 流畅度,响应更及时,优化用户体验。

I/O 性能优化

  • 使用缓存减小 I/O 次数,NSCache 是专门用来管理缓存的一个类,合理利用缓存可极大提升运行效率。
    • 并发访问缓存时数据一致性问题
    • 线程安全问题,防止一边修改一边遍历
    • 查找缓存时的性能问题
    • 缓存的释放与重建,避免无用的缓存占用过多空间
  • 化零为整进行写入操做
  • 选用适合的 API
  • 选用适合的操做线程

减小离屏渲染

离屏渲染:GPU在当前屏幕缓冲区之外新开辟一个缓冲区进行渲染操做。 离屏渲染须要屡次切换上下文环境:先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束之后,将离屏缓冲区的渲染结果显示到屏幕上又须要将上下文环境从离屏切换到当前屏幕,而上下文环境的切换是一项高开销的动做。

UITableView 性能优化

  • 正确设置 reuseIdentifierUITableViewCell 进行重用
  • 设置统一规格的 Cell
  • 尽可能减小没必要要的透明视图
  • 尽可能避免渐变、图片拉伸与离屏渲染
  • Cell 是动态高度时计算并缓存行高
  • 异步请求加载 Cell 展现数据,并进行预处理,包括图片的加载、压缩,富文本的显示
  • 减小子视图的层级关系

必要时使用 Autorelease Pool

使用循环体时,考虑是否有必要采用 Autorelease Pool 对临时对象进行释放,避免占用过多内存空间。

合理进行线程分配

合理的线程分配,最终目的就是保证主线程尽可能少地处理非 UI 操做,同时将整个 App 中子线程数量控制在合理范围,以免没必要要子线程开启与切换消耗。

  • UI 与数据源操做在主线程
  • 数据库操做、日志记录、网络回调在相应的固定线程
  • 不一样业务,经过建立队列保证数据一致性

预加载与延迟加载

  • 预处理:耗时操做提早在后台线程进行处理
  • 延迟加载:必要可视内容优先加载,其余内容稍后或须要展现时再加载

参考与更多

相关文章
相关标签/搜索