前言
随着 App 功能的逐渐强大和业务上的逐渐完善, 目前对于 iOS 开发者来说, 对于 App 的优化逐渐显得尤为重要, 本篇基于 App 渲染优化上探讨一下 imageName: 的爱恨情仇, 下面以 UITabBarItem 渲染图片为例, 一步步以实践的方式进行分析.
分析
首先看下未优化前的效果图:
测试设备: iPhone7 , 系统: 12.1
细心的同学应该能够发现, 在登录进入首页, 到首页渲染结束, 中间会有一段白屏, 为什么会白屏一会而没有马上渲染首页呢, 第一感觉肯定是这中间形成主线程阻塞了, 让 UI 没有立即渲染出来, 其实事实上确实是这样, 那接下来我们通过 Instruments 分析一下哪里执行了耗时操作以至于首页渲染被阻塞了. Instruments 里面有个工具 TimeProfiler, 可以用来帮我们查看哪里有耗时操作. 关于这个工具的使用和配置网上很多介绍本篇不做重点分析了, 我直接粘调试的图片了.
通过 TimeProfiler 的结果一目了然, 在 CustomTabBarItem 里面做了什么用了 387ms. 可以在工具里面直接右键进入到到这段耗时代码的位置. 我总共测试了五个 tabbar 渲染 item 图片的耗时:
看打点日志就很恐怖了, 执行两个 imageName: 就消耗了主线程差不多 100ms 的时间, 五个 tabbar 那就是 500ms 的时间, 显然这就是上面效果图出现白屏的原因了, 实际上 imageName: 是会对图片进行解码之后再渲染的.
既然原因找到了, 那就尝试解决一下. 将这个耗时的操作放到子线程执行, 这里也是参考了 SDwebImage 的图片编解码的思路, SD 在拿到图片 data 的时候并没有将它直接转为 image 对象, 而是在子线程里面做了一个解码的操作, 这样已经被解码的图片就赋值给 imageView 的时候就不会再进行解码, 也就不会妨碍主线程了.
- - (void)decodedImageWithImageName:(NSString *)imageName block:(void(^)(UIImage *image))block {
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- @autoreleasepool{
- UIImage *image = [[UIImage imageNamed:imageName] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
- image = [UIImage decodedImageWithImage:image];
- dispatch_async(dispatch_get_main_queue(), ^{
- if(block)
- block(image);
- });
- }
- });
- }
代码实现很简单, 就是将图片的操作放入到一个全局队列中, 当然也可以自己创建一个队列去执行这个异步操作. decodedImageWithImage: 为 SD 的代码, 需要 #import "SDWebImageDecoder.h", 具体实现网上对这一块的源码解释的比较多, 很容易理解.
这样我们的图片经过这层处理之后, 我们再来看一下优化之后的效果:
从总计 500ms 降到了 6ms, 基本可以忽略不及了, 我们再在真机上面看一下优化后的效果:
总结
通过上面的分析, 实际上 imageName: 这样的 UI 函数我们天天都在用, 但是从没想过它在某些地方能产生这么大的影响. 问题的定位和解决其实都很简单, 但是这种简单的问题往往会被我们开发者忽略掉, 产生一些不好的结果, 值得反思.
来源: https://juejin.im/post/5c15d04d6fb9a049cb18a435