从滑动UITableView响应延迟,总结bug解决方法论

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

问题阶段1

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

思考过程

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

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

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

问题阶段2

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

分析过程

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

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

首先,针对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后,无法立即看到效果。团队内经过初步讨论,决定跟随新功能发一个版本专门用来验证。

问题阶段3

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

思考

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

定位过程

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

- (void)setTapToEndEditingEnabled:(BOOL)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上时,就会逐渐导致滑动响应延迟。

解决方案

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

- (void)setTapToEndEditingEnabled:(BOOL)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打回次数,在大公司更影响个人绩效考核。
  所以说,做事方向正确,比努力更重要。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,176评论 5 469
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,190评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,232评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,953评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,879评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,177评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,626评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,295评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,436评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,365评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,414评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,096评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,685评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,771评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,987评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,438评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,032评论 2 341

推荐阅读更多精彩内容