最近在项目开发中遇到了一些关于UITableViewCell的问题,当我在反复滑动tableView的时候,cell上的内容出现了变动,同时我最近也刚好在掘金上看到了另外一篇关于这个问题的文章,参考了一下并稍作深入的研究了一下UITableViewCell的复用机制,如有问题还请指正,一起提高。
关于这个复用机制,网上众说纷纭,其中大部分的说法都是先生成能覆盖一屏幕的cell,然后上下滑动的时候再把这些cell复用,比如我向下滑的时候最上面的cell移除了视图,那么就把他复用为最下面的cell。这样只需生成略多于一屏幕的cell数量就能实现功能,还节省资源。
不过UITableView毕竟没有开源,具体怎么实现的也不清楚,所以搞了一个小小的实验,直接贴一段代码。
编译环境:Xcode11.3.1、iOS13.2
//TableViewController.swift
class TableViewController: UITableViewController {
let dataSourse = ["","","","","test","test","test","test","test","test","test","test"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(TestTableViewCell.self, forCellReuseIdentifier: TestTableViewCell.reuseIdentifier)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: TestTableViewCell.reuseIdentifier) as? TestTableViewCell
print("--------cell" + String(indexPath.row) + "被添加到视图中了")
cell!.data = dataSourse[indexPath.row]
print(cell!.description)
return cell!
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSourse.count
}
}
//TestTableViewCell.swift
class TestTableViewCell: UITableViewCell {
static let reuseIdentifier = "TestTableViewCell"
let indicateLabel = UILabel()
let dataLabel = UILabel()
var data: String! {
didSet {
display()
}
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
indicateLabel.frame = CGRect(x: 0, y: 30, width: 100, height: 40)
dataLabel.frame = CGRect(x: 100, y: 30, width: 100, height: 40)
contentView.addSubview(indicateLabel)
contentView.addSubview(dataLabel)
}
func display() {
//需注意的是在data为空的情况下并没有对dataLabel进行赋值
if data == "" {
indicateLabel.text = "暂无数据"
} else {
indicateLabel.text = "有数据"
dataLabel.text = data
}
}
}
打印结果截图如下。
我发现所有的12个cell都生成了,我特地把高度拉的很高便于测试,事实上图中仅能看见8-9个cell。
这说明了在tableView调用该数据源方法时是将所有cell全部加载好了,对于这个打印结果,我其实也半信半疑,可能是刚好某种情况让我撞见了?所以还请各位大神们指点一二(手动抱拳
那么随着我向下拉视图,可以发现cell的复用状况如下:
9、10、11的第一次出现是承接了上图,那么9、10、11的第二次出现便是下拉后的输出结果,可以明显的发现,这个新的9的cell是和第一次的11的地址是一样的,就可以理解为向下滑露出的第一个cell的内容是复用自最初加载的所有的cell的最后一个,因为此时最上面的cell还不一定消失,暂时不能被复用。那么新的10、11就和上图中的0、1对应,是复用自他们的,可以保证最上面的cell已经消失,不再能够被看到。
此时再向上滑动,可以发现复用状况更新如下:
很好理解,从下向上2、1、0按顺序出现在屏幕上,对照上面两图可以发现,这个2是没被动过的,而1、0是被11、10复用过的,那么就出现了一个一开始看会觉得很离谱的情况:
明明本来没有数据的,为什么又有了呢?关于这种情况出现的原因和解决方案,可以看这篇文章,也是最近发布的一篇,大意为复用的cell的内容设置不会更改,当我没有显式的指定某个控件的内容,再进行复用就容易出问题。本例中11、10地址的cell的右侧label值被设置为了test,而再复用到1、0中时由于代码并没有显式的给右侧label赋值(无数据),就出现了这种结果。那篇文章提供了几个解决方案及其优劣讨论,我想补充一点的就是cell无论处于哪种case都可以显式的给控件上一个值,这样就避免了这种问题的出现。
ok,踩了个小坑,也探究了cell复用到底是怎么回事。对于这个cell复用,我还是对我的实验结果不够自信。。。分享一下,希望大佬们多提宝贵意见。
更新一下,当我把cell数量增到1000的时候,发现仅仅打印到cell18,由此可以推测出之前并不是把所有的cell全部加载好放在内存中,复用池数量维持在当前屏幕可展示数量的两倍的状态,如果cell总数量小于复用池数量的话就全部加载。接下来的过程和之前一样,如果下拉的话是先把复用池中最后一个取出拿来复用,也就是18,然后是0、1、2这样。