这一篇过了快三个月才输出,从这个时间跨度就可以看出来,这个问题确实很棘手。但是,这一次我们终于有了重点突破。先看看结论:
Image在版本上线后,我们的全版本卡顿趋势持续下降,稳定后和之前相比,1s阈值的卡顿下降了1%左右。从数据上看到,这次的尝试和之前相比,的确有了明显的效果。
接下来,我们说说,具体做了哪些分析和操作。
在上一篇中,我们已经得出结论,iOS13的卡顿依然收敛到页面当中来,主要集中在以下几个页面:
-
首页
-
练习完成页
-
计划详情页
-
练习中途退出页
集中分析这几个页面,以及结合之前得出的结论来看,这几个页面有几个比较值得讨论的共性:
-
使用UITableView
-
几乎都是独立Cell,重用机制实际没有启用
在不需要使用重用,但是使用UITableView就总会尝试进行Cell的复用检查,以及不必要的cell创建。
所以,针对不需要重用的列表,我们习惯使用UITableView相关API操作UI的场景,着手准备以下两种方案进行尝试:
-
使用UIScrollView手动布局,手动维护列表排版
-
依然使用UITableView,但是完全不调用
dequeueReusableCellWithReuseIdentifier
等和重用机制相关的API,直接使用懒加载或者其他方式生成相关cell然后return
随着产品有重构首页的需求,采取了第一种方式,使用UIScrollView手动管理列表,来实现一个完全没有重用机制的列表。
接下来,我们看看已经实现后的UI层级:
Image尽量保证不要有过多的UI层级,所以几乎所有的卡片都是直接贴在UIScrollView上。
具体代码的结构如下:
-
UIScrollView用来承载列表
-
每一个卡片都是一个单独的类
-
初始化各个卡片,以及整体排版
-
手动更新卡片排版
这里,我们重点看一下,手动更新卡片排版的代码:
// 首页各个模块的位置索引typedef NS_ENUM (NSInteger, YGScheduleRootViewIndex) { YGScheduleRootViewIndexFirst = 0, //第一个下标 YGScheduleRootViewIndexBanner = 0, //运营Banner的位置 YGScheduleRootViewIndexStatictis = 1, //练习数据的位置 YGScheduleRootViewIndexCalendar = 2, //日历的位置 YGScheduleRootViewIndexDay = 3, //单日安排的位置 YGScheduleRootViewIndexActivity = 4, //运营活动的位置 YGScheduleRootViewIndexSessions = 5, //参加课程的位置 YGScheduleRootViewIndexBottomBtn = 6, //底部按钮的位置 YGScheduleRootViewIndexLast = 7, //最后一个下标};- (void)updateAllView { for (NSInteger i = YGScheduleRootViewIndexCalendar; i < YGScheduleRootViewIndexLast; i++) { UIView *subView = [self subViewWithIndex:i]; [subView.layer removeAllAnimations]; } [UIView animateWithDuration:0.3 animations:^{ CGFloat top = 0; CGFloat bottom = 0; for (NSInteger i = YGScheduleRootViewIndexFirst; i < YGScheduleRootViewIndexLast; i++) { UIView *subView = [self subViewWithIndex:i]; if (subView) { if (i == YGScheduleRootViewIndexBanner || i == YGScheduleRootViewIndexStatictis || i == YGScheduleRootViewIndexDay) { subView.top = top; } else if (i == YGScheduleRootViewIndexBottomBtn) { subView.top = top + 24; } else { subView.top = top + 16; } top = subView.bottom; bottom = subView.bottom; } } self.lastViewBottom = bottom; if ((_trialView.superview && !_trialView.hidden) || (_proExpireRemindView.superview && !_proExpireRemindView.hidden)) { [self.mainScollView setContentSize:CGSizeMake(self.mainScollView.width, self.lastViewBottom + 100)]; } else { [self.mainScollView setContentSize:CGSizeMake(self.mainScollView.width, self.lastViewBottom + 50)]; } }];}
由于依然不知道iOS在这个卡顿实际的内部的渲染机制,所以方法一做了尝试上线让数据来说话,现实证明了终于找到一个切实可行的方案去解决这个问题。
但是,方法一的弊端是代码过于松散,要么自己抽离一套API方便外部使用来管理UI,要么,还需要尝试一下方法二。
所以,接下来的工作准备着手,针对练习完成页面进行方法一和方法二的ABTest测试。同时,进行方法一的控件封装。
Let’s think!
网友评论