关于iOS性能的优化,首先需要了解一下CPU和GPU,CPU是中央处理器是一台计算机的运算核心和控制核心,主要处理运算内容。GPU是图形处理器是现实卡的心脏和核心,主要处理显示和渲染内容。事实上,目前大多的性能优化都是关于智能利用GPU和CPU,平衡它们之间工作负载。
性能对 iOS 应用的开发尤其重要,如果你的应用失去反应或者很慢,失望的用户会把他们的失望写满App Store的评论。然而由于iOS设备的限制,有时搞好性能是一件难事。开发过程中你会有很多需要注意的事项,你也很容易在做出选择时忘记考虑性能影响。
iOS最全性能优化(上)http://www.jianshu.com/p/9c450e512020
iOS最全性能优化(中)http://www.jianshu.com/p/ad90e90dce4f
iOS最全性能优化(下)http://www.jianshu.com/p/25e2f1ae51d
借用别人写好的一个简单的FPS指示器YWFPSLabel,使用CADisplayLink监视FPS数值,YWFPSLabel集成也很方便,在AppDelegate的application方法里加下面两句即可
let FPSLab = YWFPSLabel(frame: CGRect())
UIApplication.shared.keyWindow!.addSubview(FPSLab)
Instuments
Instuments是Xcode套件中没有被充分利用的工具,很多iOS开发者从来没用过Instrument,特别是通过短暂培训出来的同学们,所以,很多面试官也会问性能条调优方面的知识,来判断面试的同学是否真正应用对年开发经验。
Activity Monitor
个人觉的很像Windows的任务管理器,可以查看所有的进程,以及进程的内存、cpu使用百分比等数据等,就不多介绍了,打开一看大概就知道怎么回事
Allocations
管理内存是app开发中最重要的一个方面,对于开发者来说,在程序架构中减少内存的使用通常都是使用Allocations去定位和找出减少内存使用的方式
接下来,谈一下内存泄漏的两种情况
第一种:为对象A申请了内存空间,之后再也没用过对象A,也没释放过A导致内存泄漏,这种是Leaked Memory内存泄漏
第二种:类似于递归,不断地申请内存空间导致的内存泄漏,这种情况是Abandoned Momory
此工具可以让开发者很好的了解每个方法占用内存的情况,并定位相关的代码
Allocations查看方法占用内存.png
右键就可以打开Xcode自动定位到相关占用内存方法的代码上
定位到相关代码.png
第二种情况可以根据下图的操作清晰的找到对用的代码问题
定位Abandoned Momory.png
解释一下,第二种情况我们应该如何操作,重复的执行一系列的操作时候内存不会继续增加,比如打开和关闭一个窗口,这样的操作,每一次操作的前后,内存应该是相同的,通过多次循环操作,内存不会递增下去,通过这种分析结果,观察内存分配趋势,当发现不正确的结果或者矛盾的结果,就可以研究是不是Abandoned Momory的问题,并可以修正这个问题了
Core Animation
之前说过自己写的YWFPSLabel只能检测应用内的FPS,而此工具考虑到了程序外的动画,理想的FPS值为60左右,过低的话就用该进性优化了,根据WWDC的说法,当FPS 低于45的时候,用户就会察觉到到滑动有卡顿
Core Animation.png
圈着数字红色方框中的数字,代表着FPS值,理论上60最佳,实际过程中59就可以了,说明就是很流畅的,说明一下操作方式:在手指不离开屏幕的情况下,上下滑动屏幕列表
介绍一下Deug Display中选项的作用
Color Blended Layers(混合过度绘制)
打开此选项屏幕的效果图如下:
Color Blended Layers.jpg
这个选项基于渲染程度对屏幕中的混合区域进行绿到红的高亮(也就是多个半透明图层的叠加),由于重绘的原因,混合对GPU性能会有影响,同时也是滑动或者动画掉帧的罪魁祸首之一
GPU每一帧的绘制的像素有最大限制,这个情况下可以轻易绘制整个屏幕的像素,但如果发生重叠像素的关系需要不停的重绘同一区域的,掉帧和卡顿就有可能发生
GPU会放弃绘制那些完全被其他图层遮挡的像素,但是要计算出一个图层是否被遮挡也是相当复杂并且会消耗CPU的资源,同样,合并不同图层的透明重叠元素消耗的资源也很大,所以,为了快速处理,一般不要使用透明图层,
1). 给View添加一个固定、不透明的颜色
2). 设置opaque 属性为true
但是这对性能调优的帮助并不大,因为UIView的opaque 属性默认为true,也就是说,只要不是认为设置成透明,都不会出现图层混合
而对于UIIimageView来说,不仅需要自身需要不是透明的,它的图片也不能含有alpha通道,这也上图9张图片是绿色的原因,因此图像自身的性质也可能会对结果有影响,所以你确定自己的代码没问题,还出现了混合图层可能就是图片的问题了
而针对于屏幕中的文字高亮成红色,是因为一没有给文字的label增加不透明的背景颜色,而是当UILabel内容为中文时,label的实际渲染区域要大于label的size,因为外围有了一圈的阴影,才会出现图层混合我们需要给中文的label加上如下代码:
retweededTextLab?.layer.masksToBounds = true
retweededTextLab?.backgroundColor = UIColor.groupTableViewBackground
statusLab.layer.masksToBounds = true
statusLab.backgroundColor = UIColor.white
看下效果图:
图层混合优化.png
那些label的颜色也变成蓝色的了,这里有一点需要说明一下,
1). statusLab.layer.masksToBounds = true 单独使用不会出现离屏渲染
2). 如果对label设置了圆角的话,圆角部分会离屏渲染,离屏渲染的前提是位图发生了形变
Color Hits Green and Misses Red(光栅化缓存图层的命中情况)
这个选项主要是检测我们有无滥用或正确使用layer的shouldRasterize属性.成功被缓存的layer会标注为绿色,没有成功缓存的会标注为红色。
很多视图Layer由于Shadow、Mask和Gradient等原因渲染很高,因此UIKit提供了API用于缓存这些Layer,self.layer.shouldRasterize = true系统会将这些Layer缓存成Bitmap位图供渲染使用,如果失效时便丢弃这些Bitmap重新生成。图层Rasterization栅格化好处是对刷新率影响较小,坏处是删格化处理后的Bitmap缓存需要占用内存,而且当图层需要缩放时,要对删格化后的Bitmap做额外计算。 使用这个选项后时,如果Rasterized的Layer失效,便会标注为红色,如果有效标注为绿色。当测试的应用频繁闪现出红色标注图层时,表明对图层做的Rasterization作用不大。
在测试的过程中,第一次加载时,开启光栅化的layer会显示为红色,这是很正常的,因为还没有缓存成功。但是如果在接下来的测试,。例如我们来回滚动TableView时,我们仍然发现有许多红色区域,那就需要谨慎对待了
Color Copied Image (拷贝的图片)
这个选项主要检查我们有无使用不正确图片格式,由于手机显示都是基于像素的,所以当手机要显示一张图片的时候,系统会帮我们对图片进行转化。比如一个像素占用一个字节,故而RGBA则占用了4个字节,则1920 x 1080的图片占用了7.9M左右,但是平时jpg或者png的图片并没有那么大,因为它们对图片做了压缩,但是是可逆的。所以此时,如果图片的格式不正确,则系统将图片转化为像素的时间就有可能变长。而该选项就是检测图片的格式是否是系统所支持的,若是GPU不支持的色彩格式的图片则会标记为青色,则只能由CPU来进行处理。CPU被强制生成了一些图片,然后发送到渲染服务器,而不是简单的指向原始图片的的指针。我们不希望在滚动视图的时候,CPU实时来进行处理,因为有可能会阻塞主线程。
Color Immediately (颜色立即更新)
通常 Core Animation 以每秒10此的频率更新图层的调试颜色,对于某些效果来说,这可能太慢了,这个选项可以用来设置每一帧都更新(可能会影响到渲染性能,所以不要一直都设置它)
Color Misaligned Image (图片对齐方式)
这里会高亮那些被缩放或者拉伸以及没有正确对齐到像素边界的图片,即图片Size和imageView中的Size不匹配,会使图过程片缩放,而缩放会占用CPU,所以在写代码的时候保证图片的大小匹配好imageView,如下图所示:
图片尺寸 170 * 220px
let imageView = UIImageView(frame: CGRect(x: 50, y: 100, width: 170, height: 220))
imageView.image = UIImage(named: "cat")
view.addSubview(imageView)
苹果的单位以点计算,而 imageView的尺寸是170 220 pt 而图片是 170 220px,所以相当于在屏幕上对图片方法了一倍,看效果图如下:
Color Misaligned Image.png
可以看到图片高亮成黄色显示,更改下imageView的大小
let imageView = UIImageView(frame: CGRect(x: 50, y: 100, width: 85, height: 110))
imageView.image = UIImage(named: "cat")
view.addSubview(imageView)
看下效果图
Color Misaligned Image -2.png
当imageView和image的大小一致的时候,就正常显示了
Color Offscreen- Rendered Yellow (离屏渲染)
这里会把那些需要离屏渲染的到图层高亮成黄色,而出发离屏渲染的可能有
这行代码制定了阴影路径,如果没有手动指定,Core Animation会去自动计算,这就会触发离屏渲染。如果人为指定了阴影路径,就可以免去计算,从而避免产生离屏渲染。
设置cornerRadius本身并不会导致离屏渲染,但很多时候它还需要配合layer.masksToBounds = true使用。根据之前的总结,设置masksToBounds会导致离屏渲染。解决方案是尽可能在滑动时避免设置圆角,如果必须设置圆角,可以使用光栅化技术将圆角缓存起来:
// 设置圆角label.layer.masksToBounds = truelabel.layer.cornerRadius = 8label.layer.shouldRasterize = truelabel.layer.rasterizationScale = layer.contentsScale
如果界面中有很多控件需要设置圆角,比如tableView中,当tableView有超过25个圆角,使用如下方法
view.layer.cornerRadius = 10view.maskToBounds = Yes
那么fps将会下降很多,特别是对某些控件还设置了阴影效果,更会加剧界面的卡顿、掉帧现象,对于不同的控件将采用不同的方法进行处理:
1). 对于label类,可以通过CoreGraphics来画出一个圆角的label
2). 对于imageView,通过CoreGraphics对绘画出来的image进行裁边处理,形成一个圆角的imageView,代码如下:
这个选项会对任何直接使用OpenGL绘制的图层进行高亮,如果仅仅使用UIKit或者Core Animation的API,不会有任何效果,恕我才疏学浅,所以,我在测试的时候,确实在屏幕上没有任何反应的,openGL 绘制,我也不会,所以,就不知道到底会有什么效果了,哪位大神会的话,贴段代码,给我试试啊~~~
Flash Updated Regions (Core Graphics 绘制的图层)
此选项会对重绘的内容进行高亮成黄色,也就是软件层面使用Core Graphics 绘制的图层。我测试的时候,好像有点问题,这种解释,不知道是不是我写代码的问题,所以,就不多说了
上面说的这些高亮图层,几个常用的选项在模拟器里面可以直接调试,非常方便
模拟器高亮图层.png
红框中的选项,上面都有解释,这里就不说了,勾选项,打开模拟器,一看就知道了~
麻蛋、Core Animation 部分终于扯完了,扯了好多啊。。。
Leaks
又一个灰常重要的工具,主要检查内存泄漏,在前面Allcations里面我们提到内存泄漏分两种,现在我们研究Leaked Memory, 从用户使用角度来看,内存泄漏本身不会产生什么危害,作为用户,根本感觉不到内存泄漏的存在,真正的危害在于内存泄漏的堆积,最终会耗尽系统所有的内存。我们直接看图:
界面的介绍
在 instruments 中,虽然选择了 Leaks 模板,但默认情况下也会添加 Allocations 模板.基本上凡是内存分析都会使用 Allocations 模板, 它可以监控内存分布情况。
选中 Allocations 模板3区域会显示随着时间的变化内存使用的折线图,同时在4区域会显示内存使用的详细信息,以及对象分配情况.
点击 Leaks 模板, 可以查看内存泄露情况。如果在3区域有 红X 出现, 则有内存泄露, 4区域则会显示泄露的对象.
打用leaks进行监测:点击泄露对象可以在(下图)看到它们的内存地址, 占用字节, 所属框架和响应方法等信息.打开扩展视图, 可以看到右边的跟踪堆栈信息,4 黑色代码最有可能出现内存泄漏的方法
监测结果的分析,
Time Profiler
在开发的过程中,我们经常能感觉到,点击某一按钮,或者做了某一操作,有卡顿,这就是延迟,那使用此工具,就可以揪出耗时的函数,先看一下,调试界面介绍:
time Profiler.png
根据查看的相关耗时操作,我们就可以右键定位当耗时的方法:
写一个简单例子看一下:
看上图,可以很清楚看到此方法耗时比较严重,右键打开定位到此方法的代码:
代码截图如下:
这时候,我们把循环放到子线程来做
@IBAction func btnAction(_ sender: AnyObject) {
let svc = SecondViewController()
svc.title = "第二个页面"
//全局队列异步执行
DispatchQueue.global().async {
fori in0..<8888 {
print(i)
}
}
navigationController?.pushViewController(svc, animated: true)
}
看效果图:
到这里比较重要Instrument调试工具介绍的差不多了,说一个Xcode8.0新出的功能,很好用也很重要的功能:
还是以例子说说吧,Viewcontroller里面一个button,点击跳到SecondViewcontroller,SecondViewcontroller里面有个View,view里面有个button,button点击回到ViewController,实现是通过view的属性拿到SecondviewConroller的引用,pop回去
子view的代码如下:
class SubView: UIView {
vardelegate: SecondViewController?
@IBAction func brnAction(_ sender: AnyObject) {
delegate?.navigationController!.popViewController(animated: true)
}
}
当我们从第二个控制器,回到第一个控制器的时候,我们点一下,刚那个按钮,看图:
第二个控制器和子View都内存中,我们很容易,就可以发现问题了,这是因为,SecondViewController强引用了SubView,而我们SubView也强引用了SecondViewcontroller,就造成相互强引用,引用计数器不能为0,不能销毁了,我们只要把属性前面加个weak,变成弱引用就可以了
weak vardelegate: SecondViewController?
@IBAction func brnAction(_ sender: AnyObject) {
delegate?.navigationController!.popViewController(animated: true)
}
这时候,我们从第二个控制器pop回来的时候,看下内存:
现在就没问题了,怎样这个工具是不是挺好用啊
还有一些针对TableView,collectionView的优化,有空再聊吧,
声明:
1.本文有些文字描述,来自于其他博客和文章,因为觉的描述挺好(好吧我承认、自己也写不出来),所以就拿来用了,若有不妥的地方,请告知我,我会立即删除修改
2.因为本人的水平有限,可能有写的不对的地方,欢迎各位大大多指点、不胜感激
网友评论