起因
最近的开发任务是进行性能优化,主要是提升列表(UITableView)的滚动流畅性。
在完成了提前算高、子视图层级优化几个优化步骤之后,滚动的流畅性已经有了明显的提升,平均的帧速率(fps)已经从优化之前49提高到了55。接下来通过 Instruments 的 Time Profile 工具分析,发现圆角头像的裁剪竟然占去了20%+的用户运行时间!图片的处理需要 GPU 和 CPU 的配合,本来就需要大量的处理时间,这本无可厚非。但是这么重的任务都派发到主线程肯定是会造成界面的卡顿的,而且圆角头像在项目中的使用星罗棋布,很多界面中都用到了这个控件,所以呢,对圆角头像的优化就很有必要了。
经过
优化性能,无非是将主线程从繁重的任务中解放出来,将能在后台线程完成的任务都派发到后台线程中。这里选用 NSOperation 进行多线程处理,因为 CoreGraphics 都是线程安全的,于是把图片处理(裁圆角/加边框)的过程都在后台线程中执行好,再在主线程设置图片。接下来说一下核心的技术点:
图片处理
我将图片裁剪的过程封装并暴露了两个便利接口,默认的 - (nullable UIImage )pp_imageByRoundCornerRadius: scaleSize: 会先将图片缩放到scaleSize的大小,再添加上圆角(注意默认的方法是没有边框的),另一个接口 - (nullable UIImage )pp_imageByRoundCornerRadius: scaleSize: borderWidth: borderColor: 则可以在默认方法的基础上设置边框颜色和边框的宽度。这里没有新建一个专门的处理类,而是拓展了 UIImage 类,具体的头文件如下:
图片进行圆角裁剪和加上边框主要还是采用 CoreGraphics 和 UIBezierPath 的方法,这里按下不表。值得注意的是处理的顺序应该是先缩放图片,再进行裁剪,不然边框的宽度会因为缩放而改变。
圆角头像控件
这里采用 NSOperation + NSOperationQueue 的方式进行多线程处理,在图片、边框等属性的 set 方法里调用 setNeedsLayout 方法刷新布局,同时设置 _isNeedTransform 标记位为 YES,表示需要刷新,可以提高性能。
在上面有一个标注了1的注释点,这里的条件判断是为了避免多线程时序性的问题而加的。考虑这样的一种常见情况:圆角头像控件是表格上的单元格(cell)上的子视图,某个 cell 被滑到屏幕上,于是开始头像的裁剪(这里将这个头像称为旧头像),然后在这个裁剪尚未完成的时候,这个 cell 被滑出了屏幕,然后根据新的图片裁剪圆角(这个头像称为新头像),可能出现新头像裁剪早于旧头像完成的情况,就会导致控件先设置头像为新头像,然后被慢悠悠才完成裁剪的旧头像覆盖的问题。所以这里用这个条件避免这个问题。
结果
将上述的操作都封装隐藏好,现在的圆角头像控件 PPRoundedAvatar 的使用就很简单了,
具体的代码已经丢到了 github 上了,同时支持 Cocoapods 导入项目。
有需要的可以 clone 进行使用,也欢迎 pull request 完善这个控件。
插叙
技术上的问题就说到了这里,可是做一个开源项目不仅在技术上需要反复斟酌,而且在 github 的展示、demo 的展示等地方都需要有界面的设计,可苦了我这直男审美……/(ㄒoㄒ)/~~
这也导致了我家又吉君实在是看不下去了,终于勇敢地站了出来帮我把界面的设计给做好了,当中的你来我往又是另外的故事。真的是对我家又吉君感激涕零~~~
呐,又吉君,你帮我画一辈子设计好不好吖,我带你去吃鸡锁骨、卷饼、酱香饼、枣糕还有烤冷面哦~哈哈哈哈啊哈哈哈