TabelViewCell复用机制简述
在tableview新建的时候,会新建一个复用池(reuse pool).这个复用池可能是一个队列,或者是一个链表,保存着当前的Cell.pool中的对象的复用标识符就是reuseIdentifier,标识着不同的种类的cell.所以通常可以利用这点实现下拉到tableview底部后添加一个新的tableviewcell并标示为不同的reuseIdentifier就可以和其他类型的tableviewcell相区分开了。
在UITableView创建同时,会创建一个空的复用池.之后UITableView在内部维护这个复用池.一般情况下,有两种用法,一种是在取出一个空的cell的时候再新建一个.一种是预先注册cell.之后再直接从复用池取出来用,不需要初始化.那么针对这两种不同的复用方式具体说明如下:dequeueReusableCellWithIdentifier:forIndexPath: 和 dequeueReusableCellWithIdentifier:
一、 dequeueReusableCellWithIdentifier:forIndexPath:是iOS 6之后新出的方法,调用时肯定会返回一个Cell,不必使用Cell的 initWithStyle:reuseIdentifier:进行新建,但使用时必须先进行Cell的注册,否则会报错
这种方式在UITableView初始化的时候,注册一个UITableViewCell的类;不用再做空判断;使用此方法之后,就不用再判断取出来的cell是否为空,因为取出来的cell必定存在.也就是替你做了初始化的事情。但是这种方式必须首先注册cell,否则报错,可以通过注册类或者nib文件的形式来进行注册。一般注册的动作放在初始化的地方,例如viewdidload
reason: 'unable to dequeue a cell with identifier Cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
Cell的注册方法:
1.代码创建的Cell注册方法:
- (void)registerClass:(nullableClass)cellClass forCellReuseIdentifier:(NSString*)identifierNS_AVAILABLE_IOS(6_0);
示例:
[_tableView registerClass:[UITableViewCellclass]forCellReuseIdentifier:@"ID"];
2.Xib创建的Cell注册方法:
- (void)registerNib:(nullableUINib*)nib forCellReuseIdentifier:(NSString*)identifierNS_AVAILABLE_IOS(5_0);
示例:
[_tableView registerNib:[UINibnibWithNibName:@"XXXCell"bundle:nil] forCellReuseIdentifier:@"XXXCell"];
3.在StoryBoard上创建的Cell系统会自动进行注册,不需要再注册。
二、dequeueReusableCellWithIdentifier:这个方法使用时可以不进行注册,但调用时返回的值有可能会为空,所以需要在cell的tableView:cellForRowAtIndexPath:方法里需要进行判断返回的值是否为空,如果为空需要调用 initWithStyle:reuseIdentifier:方法进行创建
例如:
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {UITableViewCell*cell = [tableView dequeueReusableCellWithIdentifier:@"ID"];if(cell ==nil) { cell = [[UITableViewCellalloc] initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:@"ID"]; }returncell;}
由复用可能引起的展示问题
UITableView中的cell可以有很多,一般会通过重用cell来达到节省内存的目的:通过为每个cell指定一个重用标识符(reuseIdentifier),即指定了单元格的种类,当cell滚出屏幕时,会将滚出屏幕的单元格放入重用的queue中,当某个未在屏幕上的单元格要显示的时候,就从这个queue中取出单元格进行重用。
但对于多变的自定义cell,有时这种重用机制会出错。比如,当一个cell含有另外一个cell不包含的控件内容,当复用机制起作用时,可能正好读到有额外控件的cell从而展示出来,造成界面上的错乱,这是很常见的问题。
网上针对这个问题往往无外乎两种方式,一个弃用重用机制,一个清除复用cell的内容。
首先就对常用的方式做一简单总结:
1. 弃用重用机制 - 从indexpath每次获取新的cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
}
2.弃用重用机制 - 为每一个cell设置不同的重用id,因为tableview只有相同重用id的cell才会被重用,实质上也是舍弃了重用
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *CellIdentifier = [NSString stringWithFormat:@"Cell%d%d", [indexPath section], [indexPath row]];//以indexPath来唯一确定cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];//出列可重用的cell if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
//...其他代码 }
3. 清除重用cell的所有的子控件 (这种方式不能通过注册nib方式,只能自定义view中复写其中的init方法从而达到效果)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
staticNSString *CellIdentifier =@"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];//出列可重用的cell
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
else
{
//删除cell的所有子视图 while([cell.contentView.subviews lastObject] != nil)
{
[(UIView*)[cell.contentView.subviews lastObject] removeFromSuperview];
}
}
//...其他代码 }
view部分的代码
4. 清除重用子控件的内容 (支持代码或者nib文件的实现)
[self.tableView registerNib:[UINib nibWithNibName:@"DownloadImageTableViewCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"downloadImageCell"];
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
DownloadImageTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"downloadImageCell" forIndexPath:indexPath];
//清除所有的内容而不是控件
[cellclearContents];
//其他操作
return cell;
}
5.通过tableview对应的复用方法来做内容的清空 (个人推荐,比较优雅达到清除界面信息的目的)
cell被重用如何提前知道? 重写cell的prepareForReuse官方头文件中有说明.当前已经被分配的cell如果被重用了(通常是滚动出屏幕外了),会调用cell的prepareForReuse通知cell.注意这里重写方法的时候,注意一定要调用父类方法[super prepareForReuse] .这个在使用cell作为网络访问的代理容器时尤其要注意,需要在这里通知取消掉前一次网络请求.不要再给这个cell发数据了.
// if the cell is reusable (has a reuse identifier), this is called just before the cell is returned from the table view method dequeueReusableCellWithIdentifier:. If you override, you MUST call super.
- (void)prepareForReuse {
[super prepareForReuse];
}
代码实例:
自定义UITableViewCell的方法有很多 发现一些人都会遇到自己定义的cell里面图片错乱的问题 这个问题往往是因为没有实现prepareForReuse这个方法导致的.
UITableViewCell在向下滚动时复用, 得用的cell就是滑出去的那些, 而滑出去的cell里显示的信息就在这里出现了 解决的方法就是在UITableViewCell的子类里实现perpareForReuse方法, 把内容清空掉
在后面的文章中会针对不同的创建tableview的方式对体现的复用和去除复用机制做以详细讲解。