ios 性能优化(一)
-
逻辑优化
- 代码封装优化
- 代码执行效率优化
-
界面优化
- 离屏渲染优化
- 界面加载优化
逻辑优化
代码封装优化
代码的封装优化主要是细化代码的功能,每个功能单独提取出来做成一个方法,当其他地方需要用到同样功能时直接调用该方法即可,无需写重复代码,减少代码量,增加代码的重用性,方便单元测试。
例如:一个过滤输入文本内容的方法,需要过滤特殊字符和表情
上面的方法虽然实现了需要的功能,但是却显不灵活。假如我只想过滤表情,只想过滤特殊字符或者想过滤其他的内容,则需要重新写一个方法来满足功能。但是功能内部的代码却大致相同。这样就增加了代码量且使得代码看起来非常臃肿。
对上面的代码进行封装优化的方案有很多种,见仁见智,主要在思路而不在方法。
例如:我选择把过滤表情单独的提取出来,成一个根据正则表达式来过滤内容的方法,而过滤特殊字符串提取出来,成一个根据传入的字符来过滤内容的方法。
这样是方法中的功能性单一,但针对性却不单一。大大的提高了代码的重用性和单元测试。
代码的封装很重要,体现程序员的编程思维的远见性,代码的可扩展性。在合作开发时,能方便他人
代码执行效率优化
执行效率的优化主要在于得到结果的快慢,例如你想要一样东西,某宝和某东都有且价格差不多,但某宝要两天才能拿到,而某东当天下午就可以拿到。当然大家都会某东啦...这就是效率的优势。
关于代码的执行效率其实还有很多地方,碍于本人目前的眼界和水平有限,后期会验证后添加更多
- 效率1:我们最常用的for循环
执行的结果:
for循环用时:0.018385 forIn循环用时:0.017044 enumerateKeysAndObjectsUsingBlock用时:0.016417看上去是enumerateKeysAndObjectsUsingBlock更快,但是执行多次后 ,你会发现,有时for快有时forin快有时enumerateKeysAndObjectsUsingBlock快。那是因为数据量比较少。
假如把上面代码里有100000个数据。
结果:
for循环明显更快,不论尝试多少次结果都是for循环明显快。之所以用时20多秒是因为循环内部打印了日志,因为打印日志是非常耗时的操作。当然可以不用在内部去打印日志。结果依然是for循环更快。而往往在开发中,for循环内部执行的操作都是比较多并且耗时的。
所以在数据量小时for,forIn,enumerateKeysAndObjectsUsingBlock都可以。在大量数据时,尽量用for循环去执行。经过测试,执行效率上NSDictionary < NSArray < NSSet 。NSSet的执行效率最高
由此可见很多时候在获取特定的数据时算法的选择会即决定了代码执行次数也决定了执行效率。作为一个开发者要了解最基本的各类算法
冒泡排序、快速排序、插入排序、归并排序、希尔排序、动态排序。这些都是提供执行效率的基本算法。必须掌握了解的,这里就不详说了。不懂的朋友度娘
离屏渲染
说到离屏渲染,需要先了解屏幕每一帧界面是如何得到的。
- 需要了解的知识点:
屏幕显示原理
首先从过去的CRT显示器原来说起,CRT的电子枪是按照上图的方式,从上到下一行一行扫描并曾现每帧画面的。为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或其他硬件)会用硬件时钟产生一系列的信号,当电子枪换到新的一行,准备进行扫描时,显示器会发送一个水平同步信号(horizonal synchronization 简称HSync),而当一帧画面绘制完成后,电子枪回复到原位准备下一帧时,会发送一个垂直同步信号(vertical synchronization 简称VSync)。显示器通常以固定频率刷新,而这个频率就是VSync信号产生的频率。尽管现在的设备都是用液晶显示屏,但是原理仍然没有改变。
目前IOS设备采用的是双缓存+垂直同步,而Android在4.1后采用的是三缓存+垂直同步。
双缓存机制:GPU会预先渲染好一帧放入下一个缓存区内,让视频控制器取出,当下一帧渲染好后,GPU会直接把视频控制器的指针指向第二个缓冲器,如此来提高效率。即屏幕显示一帧,GPU预备下一帧。而不是显示完一帧在计算下一帧。
每一帧的由来,当收到VSync信号后,首先,系统图形服务会通过CADisplayLink等机制通知App,然后,App主线程开始在CPU中计算显示内容,比如视图等创建、布局、图片解码、文本绘制等,接着,CPU计算好的内容提交到GPU区,由GPU进行变换、合成、渲染。随后GPU将渲染结果提交到帧缓冲区,等到下一次收到VSync信号时显示上一帧计算好的内容。
界面卡顿,则是所谓的帧丢失,当在一个VSync信号内(每秒60帧,每一帧1/60秒),CPU或者GPU没有计算好内容,系统就会丢弃这一帧的内容,而屏幕依然显示的是之前的内容,就造成了界面卡顿。
GPU屏幕渲染有以下两种方式:
- on-screen Rendering:在当前屏幕渲染,指的是GPU的渲染操作实在当前用于显示的屏幕缓冲区进行的;
- off-screen Rendering:在当前屏幕以外的区域渲染,既离屏渲染,指的是在当前显示的屏幕缓冲区以外的区域开辟出来的一个新的缓冲区去进行渲染操作。
由上面知识点,可以看出离屏渲染是在GPU中造成的。
- 离屏渲染:所谓的离屏渲染即是在GPU计算时,由于界面层次复杂混合度大等造成计算的复杂度过大,导致GPU需要重新创建一个额外的屏幕外缓冲区计算这个位图。当计算好后在转换到帧缓冲区。这一次的渲染是脱离了屏幕而在屏幕以外的区域渲染完成的,所以叫做离屏渲染。
创建额外的屏幕外缓冲区去计算位图,再去替换屏幕内容的代价是非常大且耗时的。解决离屏渲染是提升用户体验非常重要的点,因为离屏渲染会导致帧丢失界面卡顿资源消耗。
补充知识点:
UIView和CALayer的关系
The Relationship Between Layers and Views这里有一篇关于它俩关系的详细说明
简单的说UIView是基于CALayer进行封装的。UIView的每个属性都对应这CALayer的一个属性。
而CALayer负责显示UIView的具体内容,UIView负责提供内容和处理响应事件等,也就是说我们在手机上看见的都是CALayer所呈现的内容。下面是CALayer的结构图
CALayer结构图CALayer由三个视觉元素组成,background背景 、contents内容、border边框。而中间的contents的属性声明为var contents: AnyObject?实际上它必须是个CGImage才能显示。
造成离屏渲染的点:
- shouldRasterize(光栅化)
当设置shouldRasterize = YES时,会把光栅化的图片保存成一个bitmap缓存起来,当下一次要显示这个图层时,CPU会直接从缓存中拿取位图,传给GPU,而不需要GPU再去渲染这一部分的图层,减少GPU的渲染计算。 可以通过Instruments core animation或者模拟器 中的 Color Hits Green and Misses Red来查看图层是否被缓存了,绿色表示缓存,红色表示没有缓存。一般视图shouldRasterize默认为NO,对于经常变换的视图不要使用shouldRasterize。会造成性能消耗的浪费。
关于shouldRasterize是一个有取有舍的属性,对于那些复杂但是内容不长变的视图可以用shouldRasterize来缓存内容,减少GPU每次的计算,达到性能提高。但是要慎用,目前本人项目中还没有使用过shouldRasterize来缓存内容。
- mask(遮罩层)
屏幕上的每一个像素点是由当前像素点上多层layer通过GPU混合颜色计算出来的,视图的layer一般在最下层,阴影则在视图layer之下。mask是layer的一个属性,它也是CALayer类型的,从官方对该属性的注释可知,默认情况下mask是为nil不存在的。mask相当于一个遮罩层,覆盖在视图的layer的上层,如果视图的layer是contentLayer,那么为这个layer添加一个mask,可以用mask来控制视图显示的区域和透明度。在mask的区域内的contentLayer会被显示,而之外的将不被显示,而区域内的contentLayer将通过mask层把像素颜色传递出去,如果mask的opacity不为1,那么mask将会按照opacity值过滤contentLayer的内容。当为视图设置了mask后,mask的复杂度会决定GPU的计算复杂度,当mask的opacity不为1时或者视图的alpha不为1,那么GPU将进行多层layer的混合颜色计算。
- shadows(阴影)
阴影是直接合成一个在视图下面的layer,而不是在下面创建一个新的视图来当做阴影,当阴影的透明度不为1时,它的渲染复杂度会比较大。
- EdgeAnntialiasing(抗锯齿)
allowsEdgeAntialiasing是ios7以后提供的方法,用来抗锯齿,有时候图片缩放或者界面旋转会造成边框出现锯齿。而锯齿的计算是非常耗性能的会造成离屏渲染的。所以在出现锯齿情况下allowsEdgeAntialiasing设置为YES
- GroupOpacity(不透明)
allowsGroupOpacity是设置视图子视图在透明度上是否跟父视图一样,一般默认情况下是为YES的。如果父视图的透明度不为1,那么子视图的透明度也不会为1。在GPU渲染的时候,就会造成既要渲染子视图还要渲染子视图下面的父视图内容,然后合成视图。这样造成GPU计算复杂度增大需要离屏渲染解决。
- 复杂形状比如圆角等
这里复杂形分为两种
一种是有系统设置造成的形状,比如设置圆角用maskToBundle加cornerRadius这种是有系统剪裁形成的圆角形状。
另一种是绘制生成的形状,比如图片中有圆角区域外是透明的或者直接绘制圆角。
系统形状会造成GPU的消耗,因为剪裁会很耗性能,而绘制会造成CPU性能消耗高,因为绘制工作是由CPU造成的
- 渐变
渐变的渲染计算是非常负责好性能的。
- Color Blended layers
标示混合的图层会为红色,不透明的图层为绿色,通常我们希望绿色的区域越多越好。
Color Hits Green and Misses Red
假如我们设置viewlayer的shouldRasterize为YES,那些成功被缓存的layer会标注为绿色,反之为红色,下面会有详细介绍。 - Color copied images
标示那些被Core Animation拷贝的图片。这主要是因为该图片的色彩格式不能被GPU直接处理,需要在CPU这边做转换,假如在主线层做这个操作对性能会有一定的影响。 - Color misaligned images
被缩放的图片会被标记为黄色,像素不对齐则会标注为紫色。 - Color offscreen-rendered yellow
标示哪些layer需要做离屏渲染(offscreen-render)
从上面的点相信你已经了解到了造成离屏渲染的原因。
下面是关于离屏渲染、界面优化的方法
- (1.)圆图:
本人在经过各种测试和观看各种文章资料后百思不得其解,从原理上来说上面指出的离屏渲染的几个点确实会造成离屏渲染。但是我代码测试并查看Color Off-Screen Rendered。居然没有高亮黄色。。。纳尼!!!难道是苹果又做了优化了。。。正在查找苹果文档
接下来说说Color Blender Layer,在模拟器中旋转Debug--> Color Blender Layer。模拟器界面中出现绿色的部分表示没有透明内容,红色的表示有透明内容。对于好的程序来说,绿色越多越好,上面离屏渲染讲过了,透明会造成GPU的计算复杂度变大,需要混合颜色计算。下面来说说解决这个问题的方法
- (2.)UILabel:中如果显示的文本是英文你会发现显示的是绿色,然而换成中文后居然显示的是红色。
-
(3.)对于图片中有透明区域,这就需要根据界面与设计同学进行调整。虽然现在的处理器越来越强,这些优化微不足道,但对于一个合格的程序员而言,尽善尽美才是追求
-
(4.)异步加载绘制
知识点:对象的创建,属性的调整等都比较消耗CPU,所以尽量的使用轻量级的对象可以减少CPU的消耗,而CALayer的量级比UIVIew轻许多。所以数据或对象的创建尽量放在异步线程中执行,保证主线程的畅通快速。但包含CALayer的控件都必须在主线程中创建操作,而界面控件一般都是在viewDidLoad里创建的,而系统方法都是在主线程中执行的,具体原因这里可以要说说Runloop的原理,过段时间写一篇关于Runloop原理的文章说明吧。
(5.)界面的数据采用异步线程的方式去计算配置,当界面数据都配置完全了,在回到主线程中去设置UI
(6.)在很多时候界面的数据我会需要从网络中获取,而有时多个网络请求之间没有关联关系,我们可以采用信号量的方式,去同步请求网络数据,当所有网络数据都返回后,在开始计算配置数据
(7.)通过Storyboard创建的视图对象消耗的资源比纯代码创建对象要多很多
(8.)Block回调来异步执行任务回调(Block是个很神奇的东西,要灵活应用啊)
-
(9.)关于TableView的优化请看我另外一篇文章UITableView的性能优化
-
(10.)有次跟朋友讨论优化的时候,说道为什么微博内容多也复杂,流畅度这么高。我们改用的方法都用了,但是cell内部内容一复杂帧数就开始下降了。后来才知道,原来是自动布局的锅,再加上自己对文本内容认识深度不够。布局是非常好性能资源的,有时为了性能少用Autolayouer技术和UILabel(但实际情况好像不可能,哇咔咔)。那么选择一个好的自动布局第三方尤为重要了。微博可能是有一套非开源的布局方法吧(这里有个来自百度知道团队的开源项目可以看看代码学习学习:FDTemplateLayoutCell。)
-
(11.)图片的缩放,UIImageView的尺寸最好跟Bundle里的原图大小,因为图片的缩放是非常耗性能的。在实际开发中,需要适配不同的屏幕尺寸,这个时候就需要与设计大神们好好沟通了。我们常在开发适配的时候,会写一个比例尺寸,界面在不同屏幕下的尺寸都是按照这个比例缩放的。所以要把自己的比例告诉设计大神们才能达到不缩放。
如果还是会缩放,那么你就需要异步去把图片绘制成UIImageView大小的图片了
/**根据边距拉伸图片@param sourceImage 原图片@param edgeInsets 边距@param resizingMode 缩放模式@param size 需要拉伸的大小@param block 处理后的图片*/ +(void)imageCompress:(UIImage *)sourceImage forEdgeInsets:(UIEdgeInsets)edgeInsets resizingMode:(UIImageResizingMode)resizingMode forSize:(CGSize)size Block:(void (^)(UIImage *image))block { /*异步处理*/ dispatch_async(dispatch_get_global_queue(0, 0), ^{ UIImage *Image; Image = [sourceImage resizableImageWithCapInsets:edgeInsets resizingMode:resizingMode]; UIGraphicsBeginImageContext(CGSizeMake(size.width, size.height)); [Image drawInRect:CGRectMake(0,0,size.width, size.height)]; UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); if (block) { /* 回到主线程 */ dispatch_async(dispatch_get_main_queue(), ^{ block(newImage); }); } }); }-
(12.)避免不必要的图片缓存:通常我们会用imageNamed:来加载图片,但是这个方法会对图片进行缓存。对于一些只有特定界面才有不常用的图片用这个方法会造成一定的内存消耗,一般不常用的图片采用initWithContentsOfFile:。可以自己写一个UIImage的类别,自行判断使用哪一个方法。这个方法我有用过,但可能目前处理器性能太好或者设计同学的图片本身就很小,内存上并看不出多大差别。对于那些图片为主的App这个方法还是很有用的
-
(13.)减少文件读取次数:文件的读取是是否消耗资源的,所以在没有必要分开文件内容的情况下,尽量把内容放在一个文件中,减少消耗。例如图片的读取,第一种,多个标签图片放在一个图片中,然后根据图片进行区域绘制,这样就减少了对图片的读取时消耗CPU的性能,第二种shouldRasterize光栅化,在GPU渲染时,直接取出上次的绘制内容,来减少文件的读取和重新绘制。
作者:树下敲代码的超人
链接:https://www.jianshu.com/p/2efcf7ad2608
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
转载于:https://www.cnblogs.com/moondev/p/10201669.html
总结
以上是生活随笔为你收集整理的ios 性能优化(一)的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: MariaDB 主从同步与热备(14)
- 下一篇: [Golang学习笔记] 05 程序实体