简介
随着IPhone
手机市场的快速发展,苹果对 IOS APP
的审核和要求也更加的苛刻。所以APP
的性能是决定能否上架,能否给用户更好的用户体验的关键因素之一。性能优化主要包括启动速度、UI反馈与响应、列表的滚动流畅性、内存是否泄漏、图形动画、等方面。
造成卡顿的原因
在VSync
信号到来后,系统图形服务会通过CADisplayLink
等机制通知App
,App
主线程开始在CPU
中计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后CPU
会将计算好的内容提交到GPU去
,由GPU
进行变换、合成、渲染。随后GPU
会把渲染结果提交到帧缓冲区去,等待下一次VSync
信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个VSync
时间内,CPU
或者GPU
没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。
iOS 设备中的 CPU & GPU
CPU
加载资源,对象创建,对象调整,对象销毁,布局计算,Autolayout
,文本计算,文本渲染,图片的解码, 图像的绘制(Core Graphics
)都是在CPU上
面进行的。GPU
是一个专门为图形高并发计算而量身定做的处理单元,比CPU
使用更少的电来完成工作并且GPU
的浮点计算能力要超出CPU很多。GPU
的渲染性能要比CPU
高效很多,同时对系统的负载和消耗也更低一些,所以在开发中,我们应该尽量让CPU负责主线程的UI调动,把图形显示相关的工作交给GPU来处理,当涉及到光栅化等一些工作时,CPU
也会参与进来,这点在后面再详细描述。相对于
CPU
来说,GPU
能干的事情比较单一:接收提交的纹理(Texture
)和顶点描述(三角形),应用变换(transform
)、混合(合成)并渲染,然后输出到屏幕上。通常你所能看到的内容,主要也就是纹理(图片)和形状(三角模拟的矢量图形)两类。
CPU & GPU 协同工作
由上图可知,要在屏幕上显示视图,需要
CPU
和
GPU
一起协作,
CPU
计算好显示的内容提交到
GPU
,
GPU
渲染完成后将结果放到帧缓存区,随后视频控制器会按照
VSync
信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。
iOS
使用的是双缓冲机制。即GPU
会预先渲染好一帧放入一个缓冲区内(前帧缓存),让视频控制器读取,当下一帧渲染好后,GPU
会直接把视频控制器的指针指向第二个缓冲器(后帧缓存)。当你视频控制器已经读完一帧,准备读下一帧的时候,GPU
会等待显示器的VSync
信号发出后,前帧缓存和后帧缓存会瞬间切换,后帧缓存会变成新的前帧缓存,同时旧的前帧缓存会变成新的后帧缓存。
TabViewCell性能分析
重用思想。
(1)我们不仅要对tableview
与collectionview
的cell
进行重用,而且还要对两者的HeardView
和FooterView
进行重用。
(2)在开发中我们会用到很多重复的视图、模型,比如相似度很高的控件,数据基本类似的model
,这时候我们应该考虑如何避免重复创建这些控件和模型,而是利用现有的最少的资源实现想要东西。
(3)对于与重用标识符我们最好使用关键字Static
去修饰重用标识符,确保重用标识符在内存中之内创建一次。
(4)Views
越多就意味着需要渲染的越多,所以消耗性能就越大,所以重用很重要。Cell
内容布局和Cell动态行高。对于相对复杂度、计算量比较大的布局,我们可以在Cell
内容展示之前通过异步方式将展示内容的Frame
、cell的行高,依赖关系提前计算好,并做动态缓存,在展示的时候直接将Frame
赋值响应的控件。流程:如果缓存有Frame
,直接给控件的frame
赋值;如果缓存中没有,在Cell
内容展示之前通过异步方式将展示内容的Frame
和依赖关系提前计算好,给控件的frame
赋值,并缓存好计算的frame
。通过
instuments
中的Core Animation
调试。
(1)图层混合。
检测界面多个UI
控件叠加的情况,对比图层颜色显示,如果显示为红色,说明使用透明或者半透明的控件,致使GPU
计算这些这些图层最终的显示的颜色的时候消耗了大量的GPU
资源。检测如果显示为绿色说明没有使用透明或者半透明的控件,性能好。
优化:
1,设置opaque
属性为true
。
2,给View
设置一个不透明的颜色,没有特殊需要设置白色即可。
(2) 图片的颜色格式。
检测Cell
上的内容设置图片的颜色格式,如果GPU
不支持当前图片的颜色格式,那么就会将图片交给GPU
预先进行格式转化,转化成合适的格式需要消耗大量GPU
资源,CoreAnimation
会将这张图片标记为蓝色。那么GPU
支持什么格式呢?苹果的GPU
只解析32bit
的颜色格式,如果使用Color Copied Images
去调试发现是蓝色,那么就找设计去要张格式合适的图吧···
(3)图片大小。
查看图片大小是否正确显示。*如果image size
和imageView size
不匹配,image
会出现黄色
。要尽可能的减少黄色的出现,因为image size与imageView size不匹配,会消耗资源压缩图片。
(4)离屏渲染。
离屏渲染(Off-Screen Rendering)指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
当前屏幕渲染(On-Screen Rendering ),指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。
离屏渲染的危害,离屏渲染会先在屏幕外创建新缓冲区,离屏渲染结束后,再从离屏切到当前屏幕, 把离屏的渲染结果显示到当前屏幕上,这个上下文切换的过程是非常消耗性能的,实际开发中尽可能避免离屏渲染。
导致离屏渲染的原因:
layer.shadow
layer.allowsGroupOpacity or layer.allowsEdgeAntialiasing
layer.shouldRasterize
layer.mask
layer.masksToBounds && layer.cornerRadius
所以在Cell中尽量避免给控件图层使用shadow(阴影)、shouldRasterize(光栅化)、layer.masksToBounds && layer.cornerRadius(设置圆角)等属性。
- 图片加载方式。
(1)异步加载思想。
加载图片,从内存中查找,如果内存中有数据,则显示图片。如果内存中没有图片,查找本地资源加载到内存,显示图片。如果本地没有数据,异步下载图片,图片下载完成后显示图片,并将图片资源加载到内存和本地。运用这种流程概念能大大优化图片加载,比如SDWebImage框架。
(2)利用OC运行时机制实现Cell的滑动和加载图片。
当tableview的cell上有需要从网络获取的图片的时候,滚动tableView,异步线程会去加载图片,加载完成后主线程就会设置cell的图片,但是会造成卡顿。可以让设置图片的任务在CFRunLoopDefaultMode下进行,当滚动tableView的时候,RunLoop是在 UITrackingRunLoopMode 下进行,不去设置图片,而是当停止的时候,再去设置图片。
-(void)viewDidLoad {
[super viewDidLoad];
// 只在NSDefaultRunLoopMode下执行(刷新图片)
[self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@""] afterDelay:ti inModes:@[NSDefaultRunLoopMode]];
}
(3)本地图片加载:从Bunder
中加载图片有两种方式,imageWithName
和imageWithContentOfFile
。imageWithName
加载图片的时候会缓存图片到内存,不适合加载太大的图片,不然对消耗大量的内存性能。imageWithContentOfFile
仅加载图片不会缓存,适合加载大图片且不会被反复使用的图片,节省内存,提高性能。
(4)图片颜色格式、图片大小:如CoreAnimation
上介绍
Cell刷新。
(1)尽量减少使用reloadData
刷新Cell
,尽可能使用局部刷新。
(2)上下拉刷新功能,尽量设置一次刷新接受的数据条数在10条——15条之内,如果接受的数据过多,数据的处理的过程就越长,展示到UI
上就非常慢,界面会出现短暂的空白或者很长时间才展示,体验很差。用
DrawRect
方法绘制自定义控件
单元格中的内容可以在自定义Cell
中使用DrawRect
方法内自己绘制。Cell高度固定,如果单元格的高度固定,直接使用
RowHeight
设置高度,性能比在Cell
方法中设置高度要好。
避免使用大量的XIB文件。
因为一个程序中XIB
文件都是动态加载的,在编译的时候就会被全部加载到内存中。如果程序中使用了大量的XIB
文件,不仅影响启动程序的速度,而且还占用了大量内存空间,用户的体验大大折扣。
做任何事都不要阻塞主线程
苹果将所有关于UI
操作的内容都放在主线程(UI线程)中,原因是将所有的UI资源放在一个线程中,而且在主线程中任何属性的生命都是用非原子类型的。
(1)为了避免资源抢夺的危险;
(2)为了不用为主线程中的资源枷锁,因为加锁是非常浪费系统资源的。
(3)主线的优先级最好,放在主线程中的操作执行的更快,响应的更加灵敏,用户体验非常好。
(4)所以任何关于UI的操作都要放在主线中去操作。而无关UI的操作最好放到子线程中操作,比如上传、下载、网络请求、定时器等耗时操作。
尽量在ViewWillAppear方法中少写代码。
因为ViewWillAppear
实在视图出现之前执行,如果在这个方法中执行了很多操作,会导致程序启动很慢,影响用户体验。
集合的选择和使用
NSSArray
:是有序的一组值。用index
查询快,但是插入和删除慢。
NSDictionary
:是无序的,通过key
存储的,所以通过Key
查找很快。
Sets
:无序的一组值,通过Value
查找快,删除和插入块。
所以合理的选择数据封装格式,也更够提高软件性能。
延迟加载(懒加载)
懒加载的思想也是很重要,苹果官方封装的UI
控件,大部分都利用到了懒加载的思想,比如ViewController
的View
,cell
上的子视图等。懒加载是在用到的时候才回去加载,这样做不仅避免所用的控件同时添加到内存中,而且还防止了控件的重复创建。
声明对象时尽量避免使用New关键字,多使用init alloc.
因为使用init alloc
系统会默认调用initWithZone
方法分配内存空间,分配的原则是类型相近就近分配,而New
只是简单初始化,内存分配随机,所以使用init alloc
声明对象减少了系统寻找堆中对象浪费的资源。
缓存思想。
要想提高软件性能,开发过程中做好缓存至关重要。缓存那些经常使用到而用不怎么变化的数据。比如cell中的动态变化的行高,和图片,我们要为他们做好缓存,下次加载这些资源的时候我们就不要再重复请求,而浪费资源又消耗性能。
对开销大的对象进行约束。
比如我们经常使用的NSdateFormatter
对象,初始化非常慢,Time Profile
检测显示估算创建NSdateFormatter
对象平均耗33ms左右,设置NSdateFormatter
对象的属性也会花费大量时间。所以我们针对这样的对象要加以限制,
处理方式:
(1)用static
关键字修饰NSdateFormatter
对象。
(2)用GCD
中的dispatch_once
修饰NSdateFormatter
对象。
(3)用懒加载的方式加载NSdateFormatter
对象。这三种方式都是为了防止NSdateFormatter
对象重复初始化,减小系统开销。
正确的选择加载数据的格式。
Josn格式的数据要比XML要快。不过josn更是适合小文档数据,因为josn数据和XML的dom格式数据都是一次性将整个数据文档加载到内存中,所以用于加载小数据更合适。加载大数据我们做好采用XML的SAX格式,因为SAX是一点点的加载到内存,然后一点点的解析,是流式的,所以更适合大数据的加载。
数据存储格式的选择。
- Plist格式和对象序列化都需要读写操作,不适合存储大数据,更适合小数据的存储,因为大数据的写和读都很消耗性能。
- 大数据的本地持久化选择Sqlite和Core Data性能更好,虽然两者的性能相似,但是苹果官方更推荐使用Core Data。Core Data运用了ORM思想(对象关系映射),对象的属性与表中的字段自动映射,不需要任何SQL语句,也不需要对表进行操作,是一种纯面对对象的数据库存储操作,类似安卓开发中用到Hibenate。
数据的展示。
一个页面的数据展示最好要限制每一刷新加载数据的条数,不要过多,限制在10-20之内,新数据的展示和老数据的展示,最好是两个不同URL。还有针对页面中UI中有很多的View,在加载的策略上,完全可以采用多线程技术进行同步加载,只把上半部分放在主线程加载,下半部分放到子线中去加载,这样可以大大降低更新数据的时间,当上半部的数据初始化完毕,下半部分就已经在另一个线程中处理完毕,所以这种加载策略更能优化软件性能。
运用算法逻辑
逻辑运算方面的处理要考虑采用做合适的算法处理运算。尽量减少不要运算资源浪费。
在程序中尽量减少使用webview
因为我们程序中webView并不像Safari浏览器加载的那么快,没有相应的硬件支持等。
Autoreleasepool的正确使用
- 写基于命令行的的程序时,就是没有UI框架,如AppKit等Cocoa框架时。
- 如果你编写的循环中创建了大量的临时对象;
- 创建了新的线程。(非Cocoa程序创建线程时才需要)。
- 长时间在后台运行的任务。
程序瘦身(未更新中)
征求APP优化策略
关于APP性能优化的方法,将在项目实战中持续更新。希望小伙伴们集思广益,都能参与进来,提供更好更完美的优化策略···