一次 iOS App 优化(踩坑)之旅

1,034 阅读4分钟
原文链接: mp.weixin.qq.com

未经深思熟虑的优化是bug之源,这句话做过深度优化的同学一定能明白其中的辛酸。今天和大家分享下博主一次优化CoreText的填坑经历。记得那时候,iPhone 4s还算市面上的主流机型。

优化起因

当时正在做一款IM App,产品经理觉得每次第一次进入聊天界面的时候有点慢,而且进入之后的第一次滑动有点卡。正是由于,导致了后面一系列的优化。在开始介绍优化方案之前,先说下「首次体验问题」。

首次体验问题

首次体验是个经典的场景,很多App都有类似的问题存在。它描述的是,App新进入一个场景,由于第一次必要的资源加载,逻辑运算等所带来的延迟,而导致的用户体验延迟。

比如大家Kill进程后重新打开微信,如果快速滑动会话列表,能感觉到明显的滑动动画卡顿,而且这种卡顿只会经历一次,再次往返滑动的时候又完全流畅了。大部分的耗时是因为头像文件的磁盘io读取,和圆角绘制。资源准备好加入cache后耗时就消失了,当然头像可以异步到子线程中去绘制,但是会导致用户能看到头像“由默认头像变为真实头像”的过程,体验稍差,显然微信采取了同步绘制的机制。

当然这并不是个大问题,现在的硬件足够快,功能场景也多,偶尔一秒以内的体验延迟完全可以忍受。

回到刚才产品经理所说的慢和卡,其实也是经典的首次体验问题。第一次进入聊天界面时有很多资源需要准备:

  1. 创建Controller及相关类
  2. 读取消息列表
  3. 渲染消息

通过Instrument Profile过后,发现当时App有相当一部分时间花费在了CoreText的渲染上。当时App的文本消息是使用CoreText绘制的,而CoreText整个绘制流程当中有一步占比最重:文本消息的高度宽度计算及超链接检测。

当时脑袋一拍,就有了方案,以空间换时间,把文字高宽度和超链接的信息都存入databae,这样下次启动的时候不用重新计算,所以就有了如下代码:

BOOL needDetectLink_calculateSize = false;
if (textMsg.textWidth == 0) {
    needDetectLink_calculateSize = true;
}

if (needDetectLink_calculateSize) {
    textSize = [_Msg_Helper calcuteSizeOfAttributedMessageText:textMsg.attributedMsgString withFrame:textMsg.ctFrame lastLineWidth:&lastLineWidth];
    textMsg.isDirty = true;
}
else
{
    textSize = CGSizeMake(textMsg.textWidth, textMsg.textHeight);
}

计算完之后,再启动一个后台任务在子线程当中把计算好的信息(dirty message)存入database。优化好之后交给产品经理体验,产品经理发现确实比之前快了不少,很满意,皆大欢喜。

第一个坑:

原本优化任务开心结束了,直到一年多后测试同学突然拿着手机给我看了一条奇怪的消息:


最后一个字看起来被截掉了一小部分,一番调试之后,发现是之前缓存的文字宽度信息不对了,又花了几个小时调查为何宽度会不对,代码上看不出任何问题,而且有些文本消息展示没有问题,只有特定的消息才会出现,直到不小心瞥见手机系统的语言是日语,猛的想到会不会是这两种系统语言下中文字体不同,一调查果不其然。

先使用中文系统发送文本消息,再切换到日语系统就能大概率重现上述问题,虽然场景比较少,毕竟是个bug,还是修一修:

BOOL needDetectLink_calculateSize = false;
if (textMsg.textWidth == 0 || preSysLanguage != curSysLanguage) {
    needDetectLink_calculateSize = true;
}

心想还是挺简单的,判断下渲染时的系统语言就可以了。

第二个坑:

又过了一年多,iOS 9发布,测试同学又过来给我看了如下画面:


我第一反应是不是换语言了,可是换语言的场景我处理过了,根据之前的思路很可能是换了字体,顺着思路一想,哦,原来是iOS 9系统换了中文字体。所以按常理我应该把代码改成这样:

BOOL needDetectLink_calculateSize = false;
if (textMsg.textWidth == 0 || preSysLanguage != curSysLanguage || preiOSVersion != curiOSVersion) {
    needDetectLink_calculateSize = true;
}

这样总可以了,或者更保险一点,我判断前后两次渲染所用的字体是否一致。可连续两次的意外让我对这段代码产生了怀疑,最后抚摸着我手里丝滑顺畅硬件指数爆表的iPhone 5s,我想到了一个更好的方案。

最终方案:

我把优化关闭了。

欢迎关注公众号: