一次iOS App优化(踩坑)之旅
lxqljc
8年前
<p>未经深思熟虑的优化是bug之源,这句话做过深度优化的同学一定能明白其中的辛酸。今天和大家分享下博主一次优化CoreText的填坑经历。记得那时候,iPhone 4s还算市面上的主流机型。</p> <h3><strong>优化起因</strong></h3> <p>当时正在做一款IM App,产品经理觉得每次第一次进入聊天界面的时候有点慢,而且进入之后的第一次滑动有点卡。正是由于 <strong>慢</strong> 和 <strong>卡</strong> ,导致了后面一系列的优化。在开始介绍优化方案之前,先说下「首次体验问题」。</p> <p>首次体验问题</p> <p>首次体验是个经典的场景,很多App都有类似的问题存在。它描述的是,App新进入一个场景,由于第一次必要的资源加载,逻辑运算等所带来的延迟,而导致的用户体验延迟。</p> <p>比如大家Kill进程后重新打开微信,如果快速滑动会话列表,能感觉到明显的滑动动画卡顿,而且这种卡顿只会经历一次,再次往返滑动的时候又完全流畅了。大部分的耗时是因为头像文件的磁盘io读取,和圆角绘制。资源准备好加入cache后耗时就消失了,当然头像可以异步到子线程中去绘制,但是会导致用户能看到头像“由默认头像变为真实头像”的过程,体验稍差,显然微信采取了同步绘制的机制。</p> <p>当然这并不是个大问题,现在的硬件足够快,功能场景也多,偶尔一秒以内的体验延迟完全可以忍受。</p> <p>回到刚才产品经理所说的慢和卡,其实也是经典的首次体验问题。第一次进入聊天界面时有很多资源需要准备:</p> <ol> <li> <p>创建Controller及相关类</p> </li> <li> <p>读取消息列表</p> </li> <li> <p>渲染消息</p> </li> </ol> <p>通过Instrument Profile过后,发现当时App有相当一部分时间花费在了CoreText的渲染上。当时App的文本消息是使用CoreText绘制的,而CoreText整个绘制流程当中有一步占比最重:文本消息的高度宽度计算及超链接检测。</p> <p>当时脑袋一拍,就有了方案,以空间换时间,把文字高宽度和超链接的信息都存入databae,这样下次启动的时候不用重新计算,所以就有了如下代码:</p> <pre> <code class="language-objectivec">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); }</code></pre> <p>计算完之后,再启动一个后台任务在子线程当中把计算好的信息(dirty message)存入database。优化好之后交给产品经理体验,产品经理发现确实比之前快了不少,很满意,皆大欢喜。</p> <p>第一个坑:</p> <p>原本优化任务开心结束了,直到一年多后测试同学突然拿着手机给我看了一条奇怪的消息:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/0ebd5da277c4a3193121c1c9b290c39f.png"></p> <p>最后一个字看起来被截掉了一小部分,一番调试之后,发现是之前缓存的文字宽度信息不对了,又花了几个小时调查为何宽度会不对,代码上看不出任何问题,而且有些文本消息展示没有问题,只有特定的消息才会出现,直到不小心瞥见手机系统的语言是日语,猛的想到会不会是这两种系统语言下中文字体不同,一调查果不其然。</p> <p>先使用中文系统发送文本消息,再切换到日语系统就能大概率重现上述问题,虽然场景比较少,毕竟是个bug,还是修一修:</p> <pre> <code class="language-objectivec">BOOL needDetectLink_calculateSize = false; if (textMsg.textWidth == 0 || preSysLanguage != curSysLanguage) { needDetectLink_calculateSize = true; }</code></pre> <p>心想还是挺简单的,判断下渲染时的系统语言就可以了。</p> <p>第二个坑:</p> <p>又过了一年多,iOS 9发布,测试同学又过来给我看了如下画面:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/0ebd5da277c4a3193121c1c9b290c39f.png" alt="一次iOS App优化(踩坑)之旅" width="750" height="179"></p> <p>我第一反应是不是换语言了,可是换语言的场景我处理过了,根据之前的思路很可能是换了字体,顺着思路一想,哦,原来是iOS 9系统换了中文字体。所以按常理我应该把代码改成这样:</p> <pre> <code class="language-objectivec">BOOL needDetectLink_calculateSize = false; if (textMsg.textWidth == 0 || preSysLanguage != curSysLanguage || preiOSVersion != curiOSVersion) { needDetectLink_calculateSize = true; }</code></pre> <p>这样总可以了,或者更保险一点,我判断前后两次渲染所用的字体是否一致。可连续两次的意外让我对这段代码产生了怀疑,最后抚摸着我手里丝滑顺畅硬件指数爆表的iPhone 5s,我想到了一个更好的方案。</p> <p>最终方案:</p> <p>我把优化关闭了。</p> <p> </p> <p> </p> <p>来自:http://mrpeak.cn/blog/coretext-performance/</p> <p> </p>