目录
启动优化
启动的三个阶段
main函数之前 - main函数之后 - 首屏渲染完成后
main函数之前
加载可执行文件
加载动态库(rebase 指针修正/binding 绑定具体的符号地址)
-objc
进行类、分类的注册,将分类方法插入到类方法列表
初始化:+load方法的调用,创建全局变量
-swift
构造函数
优化手段
减少动态库的使用
减少不使用的分类代码
减少+load方法的使用,比如方法交换可以放到+intiallize方法里
main函数之后
main函数执行开始到首屏开始绘制
这一步主要是业务逻辑的梳理,只保留必须得初始化功能,比如登录状态管理,crash监控等
非必须得功能延后,比如:埋点、统计、更新检测,这些可以在首屏渲染完成后(viewdidapear方法里)放在子线程初始化
首屏渲染完成之后
用户已经能够看到界面,viewdidappear
这里主要是防止界面卡住,因为用户已经能够看到,可能会进行滑动或点击操作。
优化手段
数据量较大时使用分页先加载少量数据
异步加载图片
还可以使用骨架屏,数据回来时填充页面
衡量标准
instrument 里的 timer profile
三方sdk
包大小优化
资源文件
图片处理
1、asset管理
使不同的设备只包含对应的图片,比如只包含3x,舍弃2x和1x图
2、三方静态检测工具
LSUnusedResources,FengNiao,可一帮助我们筛选出不用的图片和重复的图片。我们二次确认后进行删除
3、使用png图片
之前的Xcode有两个编译项Compress PNG Files 和 Remove Text Metadata From PNG Files 会对png图片进行压缩和去除非必要信息比如图片的作者、时间、注释信息等。现在的Xcode已经没有这两个选项,猜测是默认选项了。
4、大图使用webp
webp比png的压缩率更高渲染效果更好。
其它资源文件处理
1、压缩
音视频或其它资源文件进行压缩,app启动后在合适的时机进行解压缩
2、远端化
如果优先级不高可以远端化,放在服务端,在需要的时候进行下载
可执行文件
剥离符号表
1、发布版本选择,dwarf with dsym file
将调试符号表和可执行文件剥离,大方的调试符号表的大小在80M左右
2、Xcode提供了很多strip相关的选项
比如 dead code strip(链接阶段进行标记), strip linked product(链接完成后进行剥离) 会将链接过程中未使用的符号进行清除
剥离未使用代码
1、在使用三方库的时候 配置other link flag : -all_load、 -objc、 -force_load ,
-objc会加载所有的分类代码,比如一些三方库写的一些颜色字符串等的扩展代码我们实际用不到也会被加载进来。-all_load除了加载所有的分类代码还会加载一些无用的c/c++代码增加包体积。我们可以使用 -force_load加载指定的三方库的分类代码而不是全部分类代码来达到减少包体积的效果
可以配合link map 进行初步的锁定(linkmap可以看到加载了哪些分类,我们进行确认这些分类是否有真正使用。另外linkmap也可以看到哪些函数占用过大,正常函数占用大小在几十到几百字节,如果函数占用在几十KB,我们就需要优化这个函数实现,也可以减少包体积大小。函数内使用了大字符串常量,大数组,过多的ifelse都会导致函数过大)
2、三方sdk使用子模块
如果项目是组件化模式的推动其它团队发布子模块模式的库减少冗余代码的引入
3、三方的静态分析工具
帮助我们筛选出工程中未使用的代码
4、代码段迁移
mach-o文件中占比最大的部分是代码段,大字符串常量和重复字符串常量,数组、字典常量都会增加代码段。
通过将这些常量变成资源文件,资源文件会被AppStore压缩,减少包体积
通过配置相应的连接器参数,也可以对代码段进行优化:比如合并字符串常量
界面优化
一、渲染原理
CPU
文字排版、图片解压缩,布局计算(layoutSubView),位图绘制(drawRect),然后交给GPU
GPU
图层合成,图层栅格化,将数据放入帧缓冲区
垂直同步信号
垂直同步信号来的时候读取帧缓冲区的数据,显示到屏幕上
二、产生卡顿的原因
以屏幕刷新率60帧为例,一帧的时间是16ms
如果CPU和GPU在这个时间内没有准备好数据
那么垂直同步信号来的时候,读取的数据就是上一帧的数据,用户的感受就是卡顿
三、离屏渲染
gpu在渲染的时候就像画画
帧缓冲区相当于画板,对应当前屏幕上显示图像
下一帧数据来的时候,就相当于在当前画板上直接画出来,会覆盖之前的内容
但是圆角、阴影需要画完之后才知道在哪个位置添加,所以就不知道怎么画,然后就拿出另一个小画板在这个上边画,画好之后加上阴影圆角都正好了,啪排在屏幕上
但这种操作多了额外的缓存和额外的合成,所以会降低性能,尤其是在列表快速滑动的时候
maskToBounds、cornerRadius、shadow 会触发离屏渲染、降低性能
使用本身就带圆角和阴影的图片,使用贝塞尔曲线画圆角,shapelayer画阴影
四、异步绘制
UIKit的绘制
在主线程调用drawRect
文字使用coreText进行排版绘制
图片解压缩后,使用core graphic 进行绘制
当遇到超长富文本、图片加圆角阴影裁剪,复杂的界面布局,的时候主线程压力就会过大造成卡顿
比如京东和美团的商品列表
解决方案
在子线程使用core text 绘制文字,使用core graphic绘制图片,生成一张位图
在主线程交给view的layer
好处
降低了主线程的压力,减少了卡顿
提升了复杂列表的性能,因为变成平铺了
坏处
界面闪烁,因为是异步的所以显示会延迟造成闪烁,通过占位图解决
丢失事件:
在绘制时记录每个子组件的位置,点击的时候遍历所有子view,进行位置判断,处理对应事件
创建一个node对象,一个node对象对应一个子view,node内存储了view的位置信息
被点击时的处理逻辑:存放子node的数组构建一个树结构,每个node还有hitTest/pointInSide方法
五、优化手段
优化的主要思路是
避免在主线程执行耗时操作,比如:网络请求,图片解压缩,文件IO读写,复杂计算
对界面优化主要是对列表的优化
复用cell避免重复创建,cell嵌套不要太深,不要有复杂的布局计算
Cell高度只计算一次不要重复计算
使用SDWebImage异步加载图片
不要使用maskToBounds,cornerRadius,shadow会触发离屏渲染降低性能,使用带圆角和阴影的图片或者使用贝塞尔曲线画圆角,shapeLayer画阴影
对于特别复杂的cell,比如美团或京东的商品列表,使用异步渲染框架,它的思想是将复杂的cell异步绘制成一张图片然后展示,减少了主线程压力,也减少了复杂计算和离屏渲染。
内存优化
iOS通过引用计数、自动释放池、小对象等多种方式来管理内存。除此之外我们自己也要对内存的使用进行优化
内存优化的三个核心
减少内存占用量
控制生命周期
峰值管理
减少内存占用量的主要手段
1、列表cell的复用
2、复杂界面用异步绘制成单层页面
3、UIImageNamed会缓存图片,可以用于加载本地小图。contentFile不会缓存,可以加载本地大图。
4、使用sdwebimage或kingfisher加载网络图片,他们有多级缓存处理。
5、对图片降采样,图片显示多大就加载多大。比如一个10✖️10大小的imageView,其加载的图片的像素大小就是30✖️30。如果下载的图片是100✖️100像素的,这时后加载时就可以降采样
6、避免同一份数据存储多个不同的副本,比如存了model后还保留着json数据
7、列表里图片使用缩略图
控制生命周期的主要手段
1、避免循环引用:闭包、代理、定时器,容易形成循环引用,导致对象无法释放
2、KVO/Notification在界面退出时没有移除
减少峰值内存
1、数据量较大时不要一次加载,要进行分页
2、对特大图片进行分片加载,只加载能显示出来的部分
3、在循环中读取大量数据时,要使用autorelease,让临时对象及时释放
4、收到内存警告时,清理缓存
监控内存的手段
1、xcode自带的内存地图,可以帮助我们查看有哪些内存泄露
2、instrument中的leak和allocation,也可以查看内存分布和内存泄露
3、三方工具也可以帮我们查看内存泄露,有泄露时会弹窗。
4、有一个笨办法就是在页面退出时看看有没有调用dealloc
行者常至,为者常成!