UITableview优化这块,如果有心力的话是可以有很多东西去做处理的。本文目前仅就作为笔记,探讨一个方面。写这篇的文章来源于看到一篇博客,它里面先写了Tableview的两个主要协议,再通过两个协议执行方法去分析优化的方面。
上链接详细整理:UITableView优化技巧
它就Tableview的两个主要协议谈到了几点:
知道UITableViewCell的重用原理后,我们来看看UITableView的回调方法。UITableView最主要的两个回调方法是tableView:cellForRowAtIndexPath:和tableView:heightForRowAtIndexPath:。理想上我们是会认为UITableView会先调用前者,再调用后者,因为这和我们创建控件的思路是一样的,先创建它,再设置它的布局。但实际上却并非如此,我们都知道,UITableView是继承自UIScrollView的,需要先确定它的contentSize及每个Cell的位置,然后才会把重用的Cell放置到对应的位置。所以事实上,UITableView的回调顺序是先多次调用tableView:heightForRowAtIndexPath:以确定contentSize及Cell的位置,然后才会调用tableView:cellForRowAtIndexPath:,从而来显示在当前屏幕的Cell。
举个例子来说:如果现在要显示100个Cell,当前屏幕显示5个。那么刷新(reload)UITableView时,UITableView会先调用100次tableView:heightForRowAtIndexPath:方法,然后调用5次tableView:cellForRowAtIndexPath:方法;滚动屏幕时,每当Cell滚入屏幕,都会调用一次tableView:heightForRowAtIndexPath:、tableView:cellForRowAtIndexPath:方法。
看到他写的很详细,比较容易懂。 本人就着这个博客顺手写个小demo测试了一下它的执行顺序及次数。
注:
环境:macOS High Sierra 10.13.6 - Xcode 10.1(10B61) - swift4.2
tableview和cell用storyboard和xib实现,下面为部分关键代码
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("行数 ……………… 超1屏")
return 60 ;
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
print("高度 --- 超1屏")
return 1000;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("cell")
let cel = tableView.dequeueReusableCell(withIdentifier: "MyCell") as! MyCell
return cel
}
这个程序,如果按照那篇博客的说法应该是先调用了60次heightForRowAt 然后调用1次cellForRowAt。那么我实验的结果是:每次先调用3次numberOfRowsInSection,之后交互调用1次cellForRowAt和heightForRowAt,总共调
用15次,滑动的时候会依次调用cellForRowAt和heightForRowAt
这个结果和博客的结果显然有些出入。 不过,结果不一致不代表那个文章就是错的。我读了后面的评论看到以下内容:
是的,有人跟我一样去做了测试,并且也发现不一致了。因此可以推断,他当时写那篇博客的时候,可能就是那样的执行顺序,只是后面苹果又对其进行改变策略而已。结果对我们来说,已经很明了了。那目前版本执行的顺序又是什么样子的呢?我来测试看看。为了查看更多执行顺序,我把分组和行的方法都写下来:
先来测试 这个方法
func numberOfSections(in tableView: UITableView) -> Int
关键代码
func numberOfSections(in tableView: UITableView) -> Int {
print("Sections")
return 1;
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("行数 ……………… 1屏")
return 6 ;
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
print("高度 --- 少1屏")
return 100;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("cell")
let cel = tableView.dequeueReusableCell(withIdentifier: "MyCell") as! MyCell
return cel
}
结果如图:sections执行了3次。
把高度调小到1,然后再执行,发现还是结果如上图:sections执行了3次。
为了方便调试测试,我把分析的各个变量和结果做成一张表格
excel表格在后面附上链接,有兴趣的可以下载查看一下内容。
总结起来就是:
1.从执行顺序上看,这几个协议方法的执行顺序是:
numberOfSections 然后numberOfRowsInSection然后cellForRowAt最后heightForRowAt
2.从执行次序上看:
numberOfSections 每次执行3次且必执行3次
numberOfRowsInSection有多少执行多少
cellForRowAt如果所有cell的屏幕高度总和小于屏幕,则全部执行,如果总和大于屏幕,则只执行15次。
3.滑动执行顺序:是先cell再执行高。且交替执行
4.numberOfSections和numberOfRowsInSection交替执行3次,每次执行1次numberOfSections和所有次numberOfRowsInSection,执行完之后才交替执行cellForRowAt和heightForRowAt
不排除如果用代码实现的话,它执行顺序又有出入的情况。(后面有时间再测试)
至于为什么numberOfSections执行3次,cellForRowAt执行15次,应该是苹果那边的缓存机制。有其他见解的欢迎探讨。
有兴趣的人也可以进群讨论技术,技术讨论问题请加 群Q号:201708926