2015年最后一天,来总结一下iOS开发中遇到过的性能大坑,特别是UITableView这一用途非常广泛的控件。
一个UITableView为展示主题的app遇到的问题大概分为这几类:1、滚动卡顿。2、滚动没有明显卡顿但是掉帧。3、滚动崩溃。现就我开发中遇到的这些问题提出一些可能可以解决问题的地方。
导航:
1 滚动卡顿
1.1 主线程网络资源拉取冻结UI
1.2 cellForRowAtIndexPath委托中消耗CPU时间
1.3 UITableViewCell中包含大量UIImageView或其他图片容器直接加载了UIImage
1.4 Cell动态高度
2 滚动掉帧
2.1 CALayer的maskToBounds属性
2.2 Cell中太多UIView或者太多透明元素
3 滚动崩溃
1 滚动卡顿
1.1 主线程网络资源拉取冻结UI
这个问题属于老生常谈了。
解决方法:子线程下载调用主线程刷新UI。多线程现在主要用NSOperation和GCD(除了这两个还有个古老的NSThread)。NSOperation能更好的管理线程;GCD能巨方便的执行异步代码,但是管理并不方便。用法自行查看API文档。
1.2 cellForRowAtIndexPath委托中消耗CPU时间
查看UITableViewDataSource的cellForRowAtIndexPath委托是否做了太多事,或读取了较大的磁盘文件例如兆字节级别的文件,或者做了某些大量消耗CPU时间的事情例如计算兆字节级别的NSData的MD5值等,这些会在滚动过程中系统调用委托时冻结UI造成卡顿。
解决方法:把这些事情放到子线程去做。
1.3 UITableViewCell中包含大量UIImageView或其他图片容器直接加载了UIImage
这个因素经常会被开发者忽略,当UIImage赋给UIImageView的image属性时系统需要做位图解码、字节对齐、缩放适应UIImageView的contentMode等等事情。如果你的Cell中包含未压缩的大图例如几千乘几千的图片出现卡顿,可以考虑从这方面入手。
解决方法:子线程UIImage位图解码,后台绘制,且只绘制UIImageView中应该显示的大小避免缩放等。
如果这样还不能解决问题,考虑使用CALayer等轻量级控件绘制图片。CALayer不用响应用户操作,因此会带来一定的性能提升。此方法同样适用与用CALayer绘制文字代替使用UILable。
1.4 Cell动态高度
这个问题我自己开发中并没有遇到过。解决上述几个问题之后基本能解决卡顿问题,若以上处理还不能解决时可以考虑Cell动态高度计算的问题。
网络上的解决方法:缓存行高。
2 滚动掉帧
2.1 CALayer的maskToBounds属性
例如我需要在UIImageView中显示头像,这个头像是圆形的,但是我的图片是方形的。于是我设置imageView.layer.cornerRadius值改变了imageView的bounds,然后设置imageView.layer.maskToBounds = true去遮罩出圆形的头像。这样操作是没有问题的,但是千万注意,这个imageView不能放在Cell里,或者放在UIScrollView里。一旦滚动容器里有UIView设置了maskToBounds属性,你的噩梦就到来了。屏幕内存在1个设置过maskToBounds的UIView或许没感觉,2个,3个,你会很明显地发现,整个世界都变卡了。
解决方法:不设置maskToBounds属性,需要圆角图片可以事先在子线程离屏绘制然后缓存起来。
2.2 Cell中太多UIView或者太多透明元素
GPU像素填充率有限,大量透明半透明元素GPU需要重复填充,势必造成压力。
解决方法:减少透明元素,使用CALayer等轻量级控件代替UIView。
3 滚动崩溃
这个问题通常出现在数据源。滚动过程中访问了不存在的数据源出现的崩溃或其他问题。从网络拉取的数据解析之后生成Array供UITableView使用,在修改了Array以后,请务必立即在主线程reloadTableView,以免出现越界等错误。
作为完全自学的开发者,当学习到一定程度以后,掌握UIKit的基本用法以后,向着更底层的性能问题研究进军我认为是个必然,也很庆幸自己自然而然地迈出了这一步。2015年最后一天,希望2016一切顺利。