如何在iOS中快速流畅地进行表格滚动
表格视图是否以60 fps(每秒帧数)滚动? 还是看起来生涩而不光滑? 我的现在以60 fps的速度平稳运行。 它曾经不稳定,平均速度约为38 fps。 这就是我做的。
每行中具有复杂单元格的表可能难以平滑滚动。 您需要在滚动时配置和布局单元,这是有限的时间。 当需要看时,滚动会变得缓慢而生涩。 我的平均速度约为38 fps(尽管分布范围很广)。 显然很慢。 问题在于表单元格是一个自定义视图,其中包含其他自定义视图,并且这些视图的位置和大小受到各种限制。 看起来非常不错,这是一个很棒的UI。 但是,当我们的表视图控制器代表被要求提供行高时,很难很快地弄清这一点。
问题
在我们的应用程序中,表格单元格的大小可能会因数据而异,几乎相差2倍! 因此,我们在调用heightForRowAtIndexPath
方法时配置并布局一次单元,然后在调用cellForRowAtIndexPath
方法时再次进行所有操作。 我们本可以使用estimatedHeightForRowAtIndexPath
方法。 但是对我们来说,如果不进行完整的布局很难估算。
另一位开发人员希望表格准确无误。 因此,他们只是设置单元格视图并在其上进行布局,然后询问该单元格视图的高度。 那行得通,而且是准确的。 但是非常慢。
为什么我要关心的是38 fps,而不是60? 应用程序的响应性在快速动作的工作方式(如滚动等快速动作)中非常明显。 如果滚动是平滑的,则用户可以快速流畅地查看应用程序。 而且以平均38 fps的速度不规律地滚动时,会显得生涩且不流畅。 60 fps的速度与屏幕更新的速度一样快,因此必须每16毫秒更新一次(实际上由于系统和图形开销而要少)。
我在委托中的heightForRowAtIndexPath
和cellForRowAtIndexPath
方法上设置了一个断点,并看到多次调用该断点。 这段代码每次都在重做单元格布局。 有趣的是,我看到在heightForRowAtIndexPath.
之前叫做cellForRowAtIndexPath
heightForRowAtIndexPath.
与我预期的相反,但这有所帮助。
第一次尝试
我试图简化该过程,并根据行的元素和潜在的配置来计算高度。 这演变成一系列让我不安的特殊情况。 这很复杂,无法支持。 这意味着更改单元格不仅仅是更改xib文件的问题,而是再次对布局计算重新进行反向工程以估计或计算高度。
我不喜欢为要维护代码的人添加地雷。 (可能是我!)这行不通。
真正的解决方案:
因此,我回到了“布局并询问高度”方法。 但是这一次,我将在单元格上进行布局时缓存高度。
我创建了一个像这样的类:
@interface MyCachedRowHeight : NSObject
@property (nonatomic, strong) NSIndexPath *path;
@property (nonatomic,) CGFloat height;
@end
然后在我的表委托中,我创建了一个NSMutableArray
。 当cellForRowAtIndexPath
或heightForRowAtIndexPath
必须配置和布局单元格时,我将使用数组中的上述类缓存单元格的高度和路径。 在heightForRowAtIndexPath
我将首先检查高速缓存中是否存在该路径。 如果找到,它将返回该高度。
我还编写了一些方法,不仅可以缓存高度并检查缓存,还可以从缓存中删除一行或完全删除缓存内容。 这些是为了允许对给定行进行配置更改或编辑,或者允许从服务器重新获取整个表的内容。
该缓存使我达到了60 fps的目标,并使表格中的滚动变得轻松便捷。
我本可以做的
在我的情况下,每个单元只能进行一种布局。 但是,如果这不能让我达到60 fps,该怎么办? 在我的缓存中,我没有尝试优化行顺序或智能搜索。 这是一个线性搜索。 由于此高速缓存大小中的项目总数不到几百,最有可能在10到20之间,因此在这种情况下获得搜索或排序并不重要。 但是对于较大的缓存,良好的排序和智能搜索可能会有用。
原始实现中的实际成本是单元的多种配置和布局,布局特别昂贵。 由于一次调用了cellForRowAtIndexPath
,多次调用了heightForRowAtIndexPath
,并且每次调用都完成了布局,多次布局时间使我丧命。 如果布局的成本更高,并且仍然引起问题,那么我可以简单地缓存整个单元格视图。 我的方法可能会进行两次布局-一次用于高度,一次用于单元。
因为我达到了60 fps的目标,所以我不需要这样做。 这可能是因为我发现在heightForRowAtIndexPath
之前调用了cellForRowAtIndexPath
。 此呼叫顺序意味着我的表格单元只需要一个布局。 但是对于更复杂的单元并上下滚动(从而重新布置单元),单元缓存可能是有用的。
我是如何追踪的?
我使用了Xcode中的工具。 我使用Xcode菜单“产品”>“配置文件”启动了Instruments。 最初,我仅使用时间分析来查看问题的总体所在。 然后,我使用了Core Animation工具,该工具既可以进行时间分析,又可以显示每秒的帧数。 我想要的是在屏幕上流畅的动画和运动,我知道这与fps有关,因此我使用了显示动画的工具。
故事的寓意:衡量您要改进的地方。 缓存所需的数据非常昂贵。