计算bug解决方法论,质量优化相关记录

2019-09-17 作者:yzc216.com官网   |   浏览(185)

UITableView作为iOS中一个重要的基础组件,几乎是无bug的。但在开发聊天室过程中,此控件出现了一个匪夷所思的问题。由于中间又夹杂着大量功能开发,因此这个用户反馈的问题前后困扰了我3个月之久。下面我们就来回顾问题的定位过程和解决方案。

在 iOS 开发中, 我们为了跟用户一个很好的使用感受, 一般会尽可能的使 APP 的操作流程一些, 启动起诉快一些, 这也是 iPhone 中的应用应该具有的特征. 本文也是自己在平时开发过程中记录的一些优化记录与心得

偶尔有用户反馈,聊天室卡顿。

1. 尽可能不要阻塞主线程

我们知道, 在 iOS 中,UIKit 的操作一般都是在主线程中进行的, 所以一旦一旦主线程被阻塞了, 响应的 UI 可能就没有办法及时的刷新和响应事件, 这就会给使用者一种很卡顿的现象. 一般这类阻塞主线程的操作有对数据库的操作, 对文件的读写, 网络获取数据等, 所以我们尽可能的把这类操作放到子线程中去, 当完成的时候再回到主线程刷新 UI 或者进行相关操作, 以保证主线程不被阻塞, 给用户一个良好的体验.

思考过程

首先,用户提供的卡顿体验描述非常粗糙。 其次,从技术角度来说,卡顿无非就是数据计算的时间较长,占用了主线程的处理器资源,影响了UI响应的及时性。可以从数据获取、处理、展示这个流程来尝试定位。

2. 巧用 instruments

instruments 是在 iOS 开发中最 APP 性能监测的一个利器, 在需要监测性能的时候应该优先考虑使用它, 而不是去自己想象这问题可能出在了哪里. 比如需要查看APP 中哪些部分最耗时的时候, 可以使用 Time Profiler 工具, 需要查看APP 内存占用情况的时候, 可以使用 Leaks 工具. 关于 instruments 的更过相关知识, 网上也有很多的资料.

定位过程
  1. 数据获取。数据获取此处有两个渠道,一个是环信的聊天室消息实时推送,另一个是后台服务器接口给的历史消息。大致工作流程为:用户点击大厅tab,如果不在聊天室中则立即加入聊天室,并向后台接口拉取最新的历史消息若干条。聊天室加入成功后,环信会推送10条最新的聊天室消息。
  2. 数据处理。通过上述两个异步操作,获取到了初始数据,在主线程完成合并去重。之后的新消息由环信推送附加在列表末尾,而要查看更多历史消息则调用接口获取插入到列表顶部。
  3. 数据展示。调用UITableView的reloadData方法。

3. 多使用 cache (缓存)

在开发中, 为了给用户一个好的体验, 一般都会使用缓存, 例如图片数据能用 SDWebImage 这个第三方库来进行缓存. 基本思路是当 UI 需要显示图片的时候,首先去内存中查看是否有缓存数据, 如果有直接拿来用, 如果没有, 再网络获取数据, 并把最新的数据缓存在内存中去, 方便下次使用.

阶段1结论

数据获取这一过程没有过多占用主线程资源的可能。所有HTTP网络请求都是在子线程同步队列中进行,完成后在主线程回调给上层;环信消息发送可以选择子线程中异步,那么猜测其收消息应当也是子线程中接收完成后再在主线程调用自己的代理方法。 而数据处理过程是可能存在卡顿的。第一版开发的过程中,为了加快开发速度,所有cell高度的计算,都是完整地用数据构造好cell之后直接获取其高度的,因此在一个cell展示到界面的过程中,需要完成2次构造过程。 而事实确实如此,每次有新消息推过来时,如果当前正在滚动列表并且由于惯性并未停止,则刷新时有短暂的卡顿。可在深入与用户沟通后,问题并不是新消息推过来的构造过程卡顿,而是每次滑动列表时的响应延迟。

在用户反复的反馈中,我们意识到了问题的严重性。团队中的其他成员,但凡中度使用者,也都会碰到卡顿的问题。针对这个问题,团队讨论决定专门花费1天人力定位问题。

4. 懒加载 view

在使用 UITableView 的时候, 尽可能的不要在 cell 上嵌套太多的 view, 这些会影响页面滑动的流畅度, 而且越多的 view 就会花费越多的内存. 如果有太多的 view, 影响了页面滑动的流畅度, 那么就可以考虑使用懒加载 view, 即不要一次性创建太多的 view, 有一些 view 到用的时候才去创建, 会更好

分析过程

定位偶现问题需要花费大量时间,因此方向找对很重要。特定页面滑动列表响应延迟,基本可以排除数据原因,猜测的可能性有以下几种:

  1. UIScrollView中的delaysContentTouches属性导致;
  2. UITableView上是否有其他覆盖view;
  3. Cell中用到的更好支持文本链接的第三方UITextView子类控件是否改变了UITableView的行为。

5. 尽可能的减少 APP 刚刚启动时的任务

在 APP 启动的时候, 应该是尽可能快的把首页展示给用户, 以减少用户的等待时间

今天写记录这么多, 下次继续更新

定位过程

首先,针对delaysContentTouches属性的用途,要深入理解。官方文档上提到:定义:A Boolean value that determines whether the scroll view delays the handling of touch-down gestures. 可能出现的情况:If the value of this property is true, the scroll view delays handling the touch-down gesture until it can determine if scrolling is the intent. If the value is false , the scroll view immediately calls touchesShouldBegin(_:with:in:). The default value is true. 大致意思是,这个属性决定了ScrollView是否会延迟响应。用户触发touch down事件后,如果该属性为true,ScrollView会先询问代理方法touchesShouldBegin(_:with:in:)是否可以开始处理touch事件;否则,无论touchesShouldBegin(_:with:in:)返回值是什么,ScrollView都会立即响应用户操作。 理解完毕,上述所有属性设置之后,都是一锤子买卖:要么立即能响应,要么立即不能响应,不可能出现延迟响应的情况。因此猜测不成立。

对于第二种可能,借助Xcode的Debug View Hierarchy工具,查看到视图上并没有覆盖View。

而第三种猜测,由于滑动延迟本身不必现,因此将CCHLinkTextView替换为UITextView后,无法立即看到效果。团队内经过初步讨论,决定跟随新功能发一个版本专门用来验证。

经过上述各种推断和优化后,新版本仍然有用户反馈使用一段时间后滑动延迟,杀进程重启app就好了。

思考

一位安卓开发同事在和产品经理争论某bug是否值得修复时,说:“你提的bug不能必现,我改了你也不知道我改好了啊。你们得先找到必现的步骤,再来找我。” 看似不经意间的对话,却引出了一个新的方向——改bug必先复现。有经验的程序员都知道,必现的bug最好改。而不好改的,是那些偶现的问题。尽管某些问题是偶现,但能出现就一定需要满足一些触发条件。找到这个触发条件,成为解决问题的一个方向。

定位过程

用户反馈中,有一个共性:用久了就滑动延迟。 “用久了”是一个切入点。在重度使用半小时后,我自己也碰到了这个问题。并且随着使用时间越久,症状越明显。此时,忽然想到有同事反馈说从后台切出来有时就会卡顿,我尝试将app反复前后台切换,有惊人发现。反复切换200次之后,必现滑动延迟! 前后台切换app,会不断的拉取历史消息,退出、加入聊天室,刷新列表。经过一番代码屏蔽测试,查到了基类ViewController如下代码:

- setTapToEndEditingEnabled:tapToEndEditingEnabled { _tapToEndEditingEnabled = tapToEndEditingEnabled; if (tapToEndEditingEnabled) { if (_endEditingTapGesture == nil) { //TapGesture为nil时重新构造 _endEditingTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapToEndEditing:)]; _endEditingTapGesture.delegate = self; [self.view addGestureRecognizer:_endEditingTapGesture]; //每次构造TapGesture都加到ViewController的view上 } } else { [_endEditingTapGesture removeTarget:self action:@selector(tapToEndEditing:)]; //移除了TapGesture响应链 _endEditingTapGesture = nil; //置TapGesture为nil }}

刷新时,有一段switch-case:

//默认设置点击背景可dismiss掉键盘tapToEndEditingEnabled = true switch 是否输入状态 { case 文本输入状态: ... break case 语音输入状态: ... break default: //没有输入状态,需要禁用点击背景去掉键盘 tapToEndEditingEnabled = false break}

这两段代码结合起来看,等同于每次刷新都在ViewController的view上添加了一个TapGesture,并且之后在switch-case中检测到没有输入状态,又触发基类set方法把tapGesture置为了nil,但并没有将tapGesture从view上移除掉。反复刷新反复添加tapGesture,tapGesture越来越多,多到几百层覆盖在view上时,就会逐渐导致滑动响应延迟。

解决方案

问题终于找到了,遂解决之。修改基类方法如下:

- setTapToEndEditingEnabled:tapToEndEditingEnabled { _tapToEndEditingEnabled = tapToEndEditingEnabled; //要启用,且手势为nil,才构造 if (tapToEndEditingEnabled && _endEditingTapGesture == nil) { _endEditingTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapToEndEditing:)]; _endEditingTapGesture.delegate = self; [self.view addGestureRecognizer:_endEditingTapGesture]; } _endEditingTapGesture.enabled = tapToEndEditingEnabled;}

从此次解决偶现bug中可以发现,但凡遇见不能重现的问题,作为开发人员一定要从代码层面思考如何重现。一个问题不可能平白无故产生,也不会无缘无故自我修复,找到问题所在,先复现,再调整优化。之后经过相同条件测试,不再复现,才算改好。纯粹靠臆测改bug,只会增加bug打回次数,在大公司更影响个人绩效考核。 所以说,做事方向正确,比努力更重要。

本文由yzc216亚洲城发布于yzc216.com官网,转载请注明出处:计算bug解决方法论,质量优化相关记录

关键词: yzc216亚洲城 yzc216.com官网