关于UITableViewCell的一点探索
问题的起源:最近在学Cell的复用时,在网上查资料发现99%的博客简书都谈到一个“事实”,就是说到复用,表达的意思都是手机屏幕最多所能显示的cell个数,当滑动屏幕时,等到最上面第一个cell完全消失时,下面新滑出的cell会把最上面消失的那个,重新从复用池取出来然后使用,就是说,如果我的手机屏幕最多显示6个cell,那么整个UITableView只需要创建6个cell(这里为了方便我做的实验单纯针对每个cell都显示一样的东西),如果手机屏幕最多可以显示7个cell,那么整个UITableView只需要创建7个cell…,但当我自己验证的时候,却发现不是这样的,以下都是我自己的验证,不敢保证一定对,但是我都是靠打印出的数据说话的。
首先我自定义了一个cell,篇幅有限这里不过多赘述了
不懂自定义cell的可以参考一下我的这篇博客,
方便起见我下面对cell复用池的讨论都是基于我这篇博客里面自定义的cell,我新创建了一个视图控制器rootViewController,
这是我验证的代码,注意这里我设置每一个row 的行高为150,然后运行,效果如下:
我这里用的iPhone8 Plus模拟器,显然这里最多只能显示5个cell,那么按照之前所说,我的整个UITableView只需要创建5个cell,最多不能超过6个吧。但是当我打印出cell的内存地址时,是这样的:
好像不对劲???显然按照我们之前所想cell 最多最多只能创建6个,但是这里当我还没有滑动屏幕时,却打印了16个不一样的cell,也就是说当我一运行我的程序,系统就已经创建好了16个cell。???复用池机制呢?也不能说没有复用吧,因为当我一旦滑动屏幕时,下面新出现的cell 的内存地址就开始重复了,
这个时候cell才开始被复用了,可以看到 5 6 7对应的内存地址分别和15 0 1是一样的。???
所以你看这里第16(row = 15)行(row从0开始计数,所以行数等于row + 1)已经被创建出来了,但是它还没有显示到屏幕上,所以当第6(row = 5)行 滑出来时,它可以直接复用第16行的cell。
我还是不大相信,在我验证了很多比较大的行高值后,我又验证了一组比较小的行高值,比如return 35;
结果如下(由于内容太多行高较小放不下,我删除了两个label,这里只显示一个)
???这次和上面讨论的一下创建16个cell好像又不一样,其实这个倒是很好理解,因为我现在行高比较小,它可以显示19个cell,所以它会一下子创建出19个cell,然后当滑动的时候,当最上面个cell完全消失,最下面新出现的开始复用…当讨论到这里的时候,就和我搜的博客简书一致了。
所以,在做了很多组实验后,我总结得到下面的结果:
1. 对于cell一下子到底创建多少个,我认为光凭屏幕大小能容纳多少个cell来判断是不全面的,依照我做的实验,16这个数字是个神奇的数字,当你的行高超过某个值时,这个值所对应的屏幕所能容纳的cell最大个数小于16时,这个时候系统会直接创建出16个cell,然后才是复用,所以这个时候单元格复用机制所能节约的内存是建立在这16个cell基础之上的,而并不只是说只要我目前屏幕只能容纳6个cell,所以我的整个UITableView只会创建6个cell,然后复用这6个 ,当你的行高低于某个值,这个屏幕所能容纳的cell高于16个时,那么一切回归广大博客简书所述。
总而言之,cell的创建个数应该大于等于16
以上测试都是在8 Plus上得出来的,当我换成iPhone XR时,发现这个神奇的数字换成18了而不是16了,其他结果都一样。当我换成iPhone X时,这个神奇的数字又换成17了。
结论:不同机型对cell初始创建的个数不一样
所以想必这个问题得从Xcode自身或苹果公司去寻找答案了吧
以上就是我关于cell复用机制发现的一点小问题,如果有什么不对的地方,还希望大家批评指出来,毕竟我也只是刚开始学iOS,目前只是处于一个入门的阶段。
下面我想再说一说关于自定义的cell到底需不需要在viewdidload里面注册,if (cell == nil){} 或if (!cell) {}可不可以有其他方式替代这个写法。
可以发现我的第一张截图注册那个语句我是注释掉的,但是运行没有任何问题,难道注册要不要都行?其实不是这样的,在单元格创建的时候有这样一条必须要写的语句.它有两种写法:
第一种:
MyTableViewCell *cell = [_tableView dequeueReusableCellWithIdentifier:identifier];
第二种:
MyTableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
所以,你在viewDidLoad里面对不对你自定义的cell进行注册就取决于你用上面的那一种了。区别如下:
第一种:这种写法就不必在viewDidLoad里面注册了,但是你需要判断获取的cell是否为nil,也就是说如果你用第一种,就要加上 if (cell == nil ) {} 或 if (!cell){},可以理解为这两个是“配套”使用的。我上面的截图用的也是这种写法。
第二种:这种写法就必须在viewDidLoad里面进行注册了,但是就不需要写 if (cell == nil ) {} 或 if (!cell){}判断cell是否为空了。因为这种写法运行时会自动帮你实现。下面给出这种写法:
//在viewDidLoad函数里对该Cell进行注册
[self.tableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:@"myCell"];
NSString* identifier = @"myCell";
//2.根据标志符从重用池中取cell
MyTableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
//3. 这一步就可以省略了,系统会帮你实现
// if (cell == nil) {
// }
//这里就可以调用自己写的控件了
//设置它们的属性
cell.icon1.backgroundColor = [UIColor purpleColor];
cell.label1.backgroundColor = [UIColor redColor];
cell.label2.backgroundColor = [UIColor blueColor];
cell.label3.backgroundColor = [UIColor greenColor];