2021-07-16

UITableView的复用机制

UITableView 首先加载能够覆盖一屏幕的 UITableViewCell(具体个数要根据每个 cell 的高度而定)。
然后当我们往上滑动时(往下滑动同理),需要一个新的 cell 放置在列表的下方。此时,我们不去生成新的 cell 而是先从 UITableView 的复用池里去获取,该池存放了已经生成的、能够复用的 cell ,如果该池为空,才会主动创建一个新的 cell 。

复用池的 cell 是这样被添加至该池的:当我们向上滑动视图时(向下滑动同理),位于最顶部的 cell 会相应地往上运动,直至消失在屏幕上。当其消失在视图中时,会被添加至当前 UITableView 的复用池。

因此,在渲染海量数据的列表时,并不需要很多 cell ,这得益于 UITableView 的复用机制。

在使用 UITableView 时,我们可以使用 dequeueReusableCellWithIdentifier: 方法实现 cell 实例的复用

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 定义 cell 标识。
    static NSString *CellIdentifier = @"Cell";

    // 从复用池获取 cell 实例(可能为空)。
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];


    if (!cell) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }

    // 对 cell 进行简单地数据配置.
    // 偶数行的 title 文字颜色设置为红色。
    if (indexPath.row % 2 == 0) {
        [cell.titleLabel setTextColor:[UIColor redColor]];
        cell.titleLabel.text = @"Title of Even Row";
    } else {
        cell.detailLabel.text = @"Detail";
    }

   return cell;
}

在上述代码中,我们希望偶数行的标题颜色设置为红色、标题内容为 “Title of Even Row”、详情内容为空,希望奇数行的标题为空、详情内容为 “Detail” 。
但是,当我们滑动列表时,发现样式错乱了:偶数行的详情内容不为空、奇数行的标题不为空。
该问题存在的根源在于 cell 实例的复用机制:当我们没有显式地设置 cell 的样式和内容时,它会继续沿用回收前的样式和内容设置。

解决

1、方案描述:取消 cell 的复用机制,每次渲染都选择创建新的 cell 实例,将原有的 dequeueReusableCellWithIdentifier: 方法替换为 cellForRowAtIndexPath: 。
缺点:无法复用 cell ,在列表项较多时存在内存占用过大的问题。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 定义 cell 标识。
    static NSString *CellIdentifier = @"cell";

    // 通过 indexPath 创建 cell 实例,使得对于不同的 indexPath ,
    // 其对应的 cell 实例是不同的,从而解决问题。
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];

    if (!cell) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }

    // 对 cell 进行简单地数据配置.
    // 偶数行的 title 文字颜色设置为红色。
    if (indexPath.row % 2 == 0) {
        [cell.titleLabel setTextColor:[UIColor redColor]];
        cell.titleLabel.text = @"Title of Even Row";
    } else {
        cell.detailLabel.text = @"Detail";
    }

    return cell;
}

2、为每个 cell 根据 indexPath 创建唯一的标识符。

缺点:虽然可复用 cell ,但在列表项较多时仍存在内存占用过大的问题

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 根据 indexPath 定义 cell 标识。
    NSString *CellIdentifier = [NSString stringWithFormat:@"cell%ld%ld",indexPath.section,indexPath.row];

    // 从复用池获取 cell 实例(可能为空)。
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (!cell) {
        // 当复用池获取的 cell 实例为空时,需要创建新的 cell 实例。
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }

    // 对 cell 进行简单地数据配置.
    // 偶数行的 title 文字颜色设置为红色。
    if (indexPath.row % 2 == 0) {
        [cell.titleLabel setTextColor:[UIColor redColor]];
        cell.titleLabel.text = @"Title of Even Row";
    } else {
        cell.detailLabel.text = @"Detail";
    }

    return cell;
}

3、每从复用池获得一个 cell 实例时,当其不为空时,我们需要删除其所有的子视图。

该方案可实现 cell 实例的复用,成功解决了问题,应该是最好的解决方案。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 定义 cell 标识。
    static NSString *CellIdentifier = @"Cell";

    // 从复用池获取 cell 实例(可能为空)。
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (!cell) {
        // 当复用池获取的 cell 实例为空时,需要创建新的 cell 实例。
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    } else {
        // 当复用池获取的 cell 实例不为空时,删除其所有子视图,使其变“干净”。
        while ([cell.contentView.subviews lastObject] != nil) {
            [(UIView *)[cell.contentView.subviews lastObject] removeFromSuperview];
        }
    }

    // 对 cell 进行简单地数据配置.
    // 偶数行的 title 文字颜色设置为红色。
    if (indexPath.row % 2 == 0) {
        [cell.titleLabel setTextColor:[UIColor redColor]];
        cell.titleLabel.text = @"Title of Even Row";
    } else {
        cell.detailLabel.text = @"Detail";
    }

    return cell;
}

在iOS 6中dequeueReusableCellWithIdentifier:被dequeueReusableCellWithIdentifier:forIndexPath:所取代。如此一来,在表格视图中创建并添加UITableViewCell对象会变得更为精简而流畅。而且使用dequeueReusableCellWithIdentifier:forIndexPath:一定会返回cell,系统在默认没有cell可复用的时候会自动创建一个新的cell出来。

这样在tableView:cellForRowAtIndexPath:方法中就可以省掉下面这些代码:

static NSString *CellIdentifier = @"Cell";  
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];  
if (cell == nil)  {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];  
}

取而代之的是下面这句代码:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; 

自定义cell时:
1、重写自定义cell的initWithStyle:withReuseableCellIdentifier:方法进行布局

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self)
    {
        // cell页面布局
        [self setupView];
    }
    return self;
}

2、为tableView注册cell,使用registerClass:forCellReuseIdentifier:方法注册(注意是Class)

[_tableView registerClass:[xxxxxCell class] forCellReuseIdentifier:kCellIdentify];

3、在cellForRowAtIndexPath中使用dequeueReuseableCellWithIdentifier:forIndexPath:获取重用的cell,若无重用的cell,将自动使用所提供的class类创建cell并返回

xxxxxCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentify forIndexPath:indexPath];

4、获取cell时若无可重用cell,将调用cell中的initWithStyle:withReuseableCellIdentifier:方法创建新的cell

注:
1、dequeueReuseableCellWithIdentifier:与dequeueReuseableCellWithIdentifier:forIndexPath:的区别:
前者不必向tableView注册cell的Identifier,但需要判断获取的cell是否为nil;
后者则必须向table注册cell,可省略判断获取的cell是否为空,因为无可复用cell时runtime将使用注册时提供的资源去新建一个cell并返回

2、自定义cell时,记得将其他内容加到self.contentView 上,而不是直接添加到 cell 本身上

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值