要优化iOS App的性能,就得先搞清楚屏幕的成像原理.在屏幕的成像过程中,CPU 和 GPU起着至关重要的作用.
一:屏幕成像原理:
- CPU (Central Processing Unit):中央处理器
对象的创建和销毁,对象属性的调整,布局计算,文本的计算和排版,图片的格式转换和解码,图像的绘制 (Core Graphics). - GPU (Graphics Processing Unit)图形处理器
纹理的渲染.
手机要把内容要想显示到屏幕上,是需要CPU和GPU相互协作才能完成的:
CPU主要负责计算,比如说界面的布局排版,文字的大小和颜色,图片显示的解码等操作都需要CPU计算,CPU将计算好的数据提交给GPU,GPU就将CPU计算好的数据进行渲染.因为屏幕显示的数据是特定格式的,只有通过GPU渲染过的数据才能显示在屏幕上;GPU把渲染过的数据放到帧缓存区,视频控制器从帧缓存区读取数据显示到屏幕上.
CPU 和 GPU 相互协作
- iOS是双缓存机制,有前帧缓存和后帧缓存.渲染效率比较高.
- 在显示一帧数据(一屏幕数据)之前,首先会发出
垂直同步信号 (VSync)
,一旦发出垂直同步信号
就意味着要显示一帧的数据了;接下来再发送水平同步信号 (HSync)
,发出水平同步信号
就意味着一行一行的显示数据,直到填充整个屏幕为止.如果一帧的数据显示完毕,会再次发送垂直同步信号
开始下一帧数据的显示,如果把每一帧的数据连起来看起来就和动画一样了.
屏幕成像原理
二:卡顿产生的原因
有时候我们拖拽屏幕会发现屏幕不是很流畅,并且会有卡顿现象.那么产生卡顿的原因是什么呢?
CPU计算数据和GPU渲染数据都是需要消耗时间的,如果垂直同步信号
发出的时候,CPU和GPU还没有把数据处理好,就会把CPU和GPU上一次处理好的数据显示在屏幕上.而CPU和GPU当前正在计算和渲染的数据就丢失了,俗称掉帧
.也就是说屏幕上显示的还是上一帧( 上一页 )的数据,所以就造成了卡顿.而刚才CPU和GPU没有处理好的数据只有等到下一次垂直同步信号
的到来才能显示.如图所示:
- 知道了屏幕卡顿的原因,就有了解决卡顿的思路:
尽量减少CPU , GPU资源消耗,缩短CPU 和 GPU 消耗的时间
- 要保证屏幕不卡顿,就要保证1s刷新60帧,每隔16ms(毫秒)就会又一次
VSync
信号.也就是我们要在16毫秒内完成CPU的计算和GPU的渲染工作,这样就不会掉帧
三:卡顿优化
- 尽量使用轻量级的对象,比如用不到事件处理的地方,可以考虑使用
CALayer
代替UIView
. - 不要频繁的调整
UIView
的相关属性,比如:frame , bounds , transform
等,尽量减少不必要的修改.因为每次调整都要重新计算渲染,消耗的性能就比较多. - 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改.
-
Autolayout
会比直接设置frame
消耗更多的CPU资源. - 图片的
size
最好跟UIImageView
的size
保持一致.如果图片过大CPU会进行伸缩处理,消耗资源. - 控制线程的最大并发数.
- 尽量把耗时的操作放到子线程.
文本处理(尺寸计算,绘制)
图片处理(解码,绘制):通过UIImage imagename:
方法加载的图片并不能直接显示在屏幕上,这样加载的仅仅是经过压缩的图片的二进制数据,如果想要把图片渲染到屏幕上还要对图片进行解码,解码成屏幕需要的格式.所以如果图片比较多比较大的话,可以提前放到子线程进行解码.最后再放到主线程显示,这样主线程就不会有这些解码的工作就不会造成卡顿.
四:卡顿检测
到现在我们知道,卡顿
主要是因为在主线程执行了比较耗时的操作造成的.我们可以添加Observer
到主线程的runloop
中,通过监听runloop
状态切换的耗时,以达到监控卡顿的目的.
五:耗电优化
耗电操作主要包含4个方面:
- CPU处理,
Processing
- 网络,Networking
- 定位,Location
- 图像,Graphics
优化思路:
- 尽可能降低CPU,GPU功耗.
- 少用定时器.
- 优化I/O操作:
- 尽量不要频繁写入小数据,最好批量一次性写入.
- 读写大量重要数据时,考虑
dispatch_io
,其提供了基于GCD的异步操作文件I/O的API.用dispatch_io
系统会优化磁盘访问. - 数据比较大的,建议采用数据库(比如
SQLite,CoreData
)
- 网络优化:
- 减少,压缩网络数据.比如之前和服务器交互是采用
XML
,提交比较大.后来采用json
,现在有的公司已经使用protobuffer / protocol buffer
. - 如果多次请求的结果是相同的,尽量使用缓存.
NSCache
- 使用断点续传,否则网络不稳定时可能多次传输相同的内容.
- 网络不可用时不要尝试执行网络请求.
- 让用户可以取消长时间运行或者速度很慢的操作,设置合适的超时时间.
- 批量传输(减少发送网络请求的数量).比如:下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块的下载.如果下载广告,一次性多下载一些,然后慢慢展示.如果下载电子邮件,一次下载多封,不要一封一封的下载.
- 减少,压缩网络数据.比如之前和服务器交互是采用
- 定位优化:
- 如果只是需要快速确定用户位置,最好用
CLLocationManager
的requestLocation
方法.定位完成后会自动让定位硬件断点. - 如果不是导航引用,尽量不要实时更新位置,定位完毕就关掉定位服务.
- 尽量降低定位精度.比如尽量不要使用精度最高的
KCLLocationAccuracyBest
. - 需要后台定位时,尽量设置
pauseLocationUpdatesAutomatically
为YES
.如果用户不太可能移动的时候,系统会自动暂停位置的更新. - 尽量不要使用
startMonitoringSignificantLocationChanges
,优先考虑startMonitoringForRegion
.
- 如果只是需要快速确定用户位置,最好用
- 硬件检测优化:
用户移动,摇晃,倾斜设备时,会产生动作(motion
)事件.这些事件由加速计,陀螺仪,磁力计等硬件检测.在不需要检测的场合,应该及时关闭这些硬件.
六:App启动优化
App的启动可以分为两种:
- 冷启动(
Cold Launch
):从零开始启动App. - 热启动(
Warm Launch
):App已经在内存中,在后台存活着,再次点击图标启动App.
而我们要做的启动优化,主要是对冷启动进行优化.一般来说,冷启动的时间只要在400毫秒以内就没有问题.超过400毫秒就需要优化了.
通过添加环境变量可以打印出App的启动时间分析:
Edit scheme -> Run -> Arguments -> Envirnvironment Variables
下添加:-
DYLD_PRINT_STATISTICS
设置为1:打印粗略信息. -
DYLD_PRINT_STATISTICS_DETAILS
设置为1:打印详细信息
-
- App的冷启动可以概括为3大阶段:
-
dyld
:加载可执行文件,加载动态库阶段. -
runtime
:初始化OC结构,比如初始化类,分类等等. -
main
:调用main
函数.
-
启动优化分析:
-
dyld
:
我们点击App图标启动一个App,最开始是由dyld ( dynamic link editor )
的程序去装载App的可执行文件.dyld
是Apple
的动态链接器,不管是iOS项目还是Mac OS 项目都会用到它.它是用来装载Mach-O
文件的.(可执行文件,动态库等等).
可执行文件包含我们所写的代码,并不包括项目中用到的动态库(比如:UIKit \ UIFondation)
,可执行文件中只有动态库的依赖信息,dyld
出了要加载可执行文件到内存中以外,还要检查Mach-O
文件依赖哪些动态库,然后去加载这些动态库.加载动态库的时候还会检查这个动态库是否还依赖其他动态库,如果依赖就递归查找加载到内存.
等dyld
把可执行文件和动态库都加载到内存中后,就会通知runtime
进行下一步处理. -
Runtime
:
启动App时,runtime
所做的事情:- 调用
map_images
进行可执行文件内容的解析和处理. - 在
load_images
中调用call_load_methods
,调用所有class
和category
的+load
方法. - 进行各种
objc
结构的初始化.(注册Objc类
,初始化类对象等等). - 调用
C++
动态初始化器 和__attribute__((constructor))
修饰的函数.
到此为止,可执行文件和动态库中的所有符号(Class , Protocol , Selector , IMP , ...)
都已经按格式成功加载到内存中,被runtime
所管理.整个OC的运行环境就已经搭建好了.
- 调用
总结:
- App的启动由
dyld
主导,将可执行文件加载到内存,顺便加载所有依赖的动态库. - 并有
Runtime
负责加载成objc
定义的结构. - 所有初始化工作结束后,
dyld
就会调用main
函数. - 接下来就是
UIApplicationMain
函数,AppDelegate
的didFinishLaunchingWithOptions:
方法.
启动优化思路:
-
dyld
:- 减少动态库,合并动态库(定期清理不必要的动态库).
- 减少
Objc
类,分类的数量,减少Selector
数量.(定期清理不必要的类,分类). - 减少
C++
虚函数的数量.一旦有虚函数就会多维护一张虚表. -
Swift
尽量使用struct
,不要使用类.
-
runtime
:
用+initialize
方法和dispatch_once
取代所有的__attribute__((constructor))
,C++ 动态构造器
,Objc 的 +load
方法. -
main
:- 在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部放在
didFinishLaunchingWithOptions
方法中. - 按需加载.
- 在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部放在
七:安装包瘦身
安装包主要由可执行文件,资源组成.
-
针对资源
(图片,音频,视频 等)
:- 采取无损压缩.
- 去除没有用到的资源.可以用github上的此工具.
-
可执行文件的瘦身:
- 编译器优化:
- Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为YES
- 去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO, Other C Flags添加-fno-exceptions
- 利用AppCode检测未使用的代码:菜单栏 -> Code -> Inspect Code
- 编译器优化:
-
生成LinkMap文件,可以查看可执行文件的具体组成
image.png -
可借助第三方工具解析
LinkMap
文件.
网友评论