TableViewCell reuse 重用 我认为的正确理解与使用方法
其实有点失望,因为用google搜索“uitableviewcell dequeueReusableCellWithIdentifier”出来一堆堆资深博主的文章。看了看,大部分都是在解决一个问题:使用重用时cell显示混乱的问题。该问题本身并不让我失望,失望的是博主们的解释。
首先,回顾一下UITableViewCell的重用,其基本逻辑就是tableView一开始会创建一屏幕的cell(如果有那么多)并把他们标记(Identifier),之后用户上下滑动tableView时,使用Identifier获取移出去的cell,将其用作新移进来的cell重新被赋予内容,再显示出来,这样就完成了cell重用,目的是优化内存。现在,我并不对其原理做再深入的探究,只是知道以上这些大概的逻辑。(欢迎探讨,以及指出你认为我可能理解错的地方)
问题来了,我在工作中、或是在前文提到的资深博主文章里会经常会看到有人说重用发生问题,新移入的cell有时会显示出刚刚移除去的cell的内容,或者干脆就在滑动的过程中cell显示完全混乱了。于是他们有写人就创造了一些方法和解释。比如:
http://www.cnblogs.com/lihaibo-Leao/p/3471556.html
http://blog.csdn.net/hmt20130412/article/details/20860049
http://blog.csdn.net/joiningss/article/details/6702023
http://blog.csdn.net/winsdom123456/article/details/7363383
各种解决方案归纳一下无外乎:
1,重用遇到界面混乱,这是个常见问题!我们就要使用多种的方法来干掉这个重用机制。(某博主原话,无力吐槽。。。)
2,既然重用有问题,那么我们就不重用了,有多少数据,就创建多少cell。
3,重用归重用,但是identifier用不同的(好自欺欺人啊= =#)
4,重用归重用,但我每次设置cell上的subView内容时,先删除上面的所有subView,再重新创建。
遇到问题解决问题、分享解决方法这是好的,但是这里他们忽略了几个问题:
1,iOS为什么一定要重用?重用优化内存,这是apple的全职工程师研究出来的东西,你能轻易否定它吗?
2,identifier是用来标记可被重用的cell的队列的,如果你使用不同的标记来标记cell,创建的时候也使用不同的标记创建,这是自欺欺人的,实际上也没用重用;
3,每次删除,再创建subView,看起来比较保险,也算是几个解决方案中最靠谱的方法了。。。但其实,这对效率是一种损失。重用机制本来就是希望优化内存,优化界面体验,你删除再创建,增加不必要的工作量,就与这一理念背道而驰。
吐槽完毕,现在我利用一个demo(在这里可以看到https://github.com/pigpigdaddy/TableViewTestDemo)解释一下我理解的正确的cell重用
先介绍一下demo:我创建了一个空项目,又创建了三个类,
类ViewController,用于window的rootViewController,
类TableViewTestView,在这里面创建tableview,
类DemoTableViewCell,继承自UITableViewCell,里面有一个label,加在了cell的contentView上。
在ViewController创建TableViewTestView的实例,再在TableViewTestView里创建tableView,实现各种tableView的回调、数据源。用的cell是DemoTableViewCell (具体还请一定去https://github.com/pigpigdaddy/TableViewTestDemo看源代码,很简单,我就不对每个函数做做完全的说明了)
那么正确的重用应该是:
1,首先,用户滑动tableview,在每次刷新新的cell时会自动调用
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *),你应该老老实实地去实现它
1 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 2 { 3 return self.dataSource.count; 4 } 5 6 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 7 { 8 static NSString *cellIdentifier = @"DemoTableViewCell"; 9 DemoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 10 if (!cell) { 11 cell = [[DemoTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; 12 } 13 14 [cell loadData:[self.dataSource objectAtIndex:indexPath.row]]; 15 16 return cell; 17 }
2,每次调用该函数时,就像我们所知道的那样,先去根据identifier去寻找“队列里”是否有可以重用的cell,如果没有,则创建一个新的,所用的identifier是同一个!
3,刷新数据,你只需要调用我自定义cell里的函数
- (void)loadData:(NSDictionary *)data;
1 [cell loadData:[self.dataSource objectAtIndex:indexPath.row]];
该函数负责修改label的text,你不需要先删除,在重新创建label,完全不需要。
4,那么label应该在哪里创建呢?在cell初始化的地方创建:
1 - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 2 { 3 self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; 4 if (self) { 5 // Initialization code 6 self.labelTest = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 24)]; 7 [self.contentView addSubview:self.labelTest]; 8 } 9 return self; 10 }
像这样,仅仅在这个cell被创建的时候,创建labelText,以后刷新到这个cell,只需要调用loadData这个函数,用相应的数据去刷新label内容就可以了!只要你正确刷新数据,显示就不会有问题!
运行程序,上下滑动,完全没有问题!
自定义的cell有再多的subView,这些subView只在cell创建的时候创建,其余时候刷新cell,只需要loadData刷新数据,刷新subView显示内容就可以了,就是这么简单!
再也不用担心会混乱,再也不用找各种方法去规避“重用机制”了。希望大家曾经发生混乱的都去试试!
写的不对的地方请多包涵,并帮忙指出,感谢!