野火🔥

生命如野火,骄傲而顽强

-[UIImage imageNamed:]方法导致的卡顿优化

引子

这两天追查app卡顿问题,打开time profiler查看方法耗时,惊奇发现-[UIImage imageNamed:]占用CPU时间很长:

下图是重复打开一个feed页面的CPU占用,发现20%的CPU时间在-[UIImage imageNamed:]中,显然不合理,因为我们知道-[UIImage imageNamed:]相对于-[UIImage imageWithContentsOfFile:]存在图片缓存,重复打开本地图片应该性能影响非常小。

关掉time profiler的Hide System Libraries勾选项,查看系统方法堆栈和对应的耗时:

发现-[NSFileManager fileExistsAtPath:]为主要耗时方法,该方法没有本地数据读取,只是判断是否存在,感觉一脸懵逼?难道内存缓存了还会每次线判一次本地存在?苹果的实现不会这么蠢吧?..

-[UIImage imageNamed:] 的实现原理

imageNamed:实际上调用的是一个叫做UIAssetManager的类,每个Bundle有一个UIAssetManager。它有一个strong-strong的NSMapTable的属性,用来做缓存。。。

如果查询不到缓存,首先命中的是Assets.car,这个是CoreUIFrramework处理的(私有framework),会解压(有缓存)那个Assets.car然后解码取回图。

如果还找不到,会通过Bundle的path按照那个文档描述的,搜索@3x @2x @1x .png忽略等规则,直到找到一个那种非Assets.car的bundle图,然后加载。

如果你的图片不在Assets.car里面,会直接触发到最远的第三部,可能遍历搜索fileExist比较耗时吧

最终卡顿原因

原因找到了:
themedImageNamed: 方法会每次都给 imageName 加上后缀先去取图,这个应该是iOS7之前没有Images Assets之前的适配逻辑。现在其实包里面没有-667h、-736h这样的图了,会导致所有图片第一次都取不到。就会发现频繁调用fileExistsAtPath:,而没调取图逻辑。
系统缓存了所有找到图片的UIImage,没缓存找不到的黑名单。

一些结论和启示

-[UIImage imageNamed:] 需要hook加保护
FPS优化一定要找到瓶颈,别贸然优化 --> 合理使用工具
IO操作很恐怖,哪怕只是很轻量级的处理
有必要监控一下NSFileManager的IO操作情况

Categories:  技术 

« 面试常见:iOS Category 复杂多人项目Owner思考 »