慎用 reloadRowsAtIndexPaths

慎用 reloadRowsAtIndexPaths_第1张图片
banner.jpg

reloadRowsAtIndexPaths 一般用于刷新一组 cell,笔者在使用过程中发现,调用该方法后 Tableview 并不是刷新 cell,而是会重新创建 cell。

并且 cell 的重用会发生错乱,在 IndexPath [0, 0] 下创建的 cell 会在 IndexPath [0, 1] 下被重用。

比如一个常见的需求,在 TableView 第一行显示头像,第二行显示昵称。假设这两条信息是单独从服务器获取的。

慎用 reloadRowsAtIndexPaths_第2张图片
normal.png

模拟网络请求代码如下:


- (void)viewDidLoad {
    [super viewDidLoad];
    _titles = @[@"Avator", @"Nickname"];
    [self.tableView registerClass:[SFTableViewCell class] forCellReuseIdentifier:kCellIdentifier];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDelayTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
        _avator = [UIImage imageNamed:@"cat"];
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
        [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDelayTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
            _nickName = @"Smallfly";
            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:1 inSection:0];
            [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            
        });
    });
}

但实际的的显示结果却是这样:

慎用 reloadRowsAtIndexPaths_第3张图片
actual.png

再来看一下 TableView DataSource 的实现:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // Why reloadRowsAtIndexPaths [0,0] returned cell is nil?
    SFTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier forIndexPath:indexPath];
    cell.textLabel.text = _titles[indexPath.row];

    if (indexPath.row == 0) {
        [self configureCell:cell indexPath:indexPath];
    } else {
        NSString *nickName =  _nickName ?: @"nickname";
        cell.detailTextLabel.text = nickName;
    }
    return cell;
}

在 indexPath.row == 0 的时候为 cell 添加头像:

- (void)configureCell:(UITableViewCell *)cell indexPath:(NSIndexPath *)indexPath {
    
    for (UIView *sv in cell.contentView.subviews) {
        if ([sv isKindOfClass:[UIImageView class]]) {
            [sv removeFromSuperview];
        }
    }
    
    UIImage *avator = _avator ?: [UIImage imageNamed:@"user_profile_avatar"];
    UIImageView *avatorImageView = [[UIImageView alloc] initWithImage:avator];
    // configure imageView...
    [cell.contentView addSubview:avatorImageView];
}

为防止重复的添加 imageView ,先将存在的头像移除。

进行一番调试之后笔者发现,在请求前,两个 cell 的地址为:

 // indexPath [0, 0]
 // indexPath [0, 1]

在请求后调用 reloadRowsAtIndexPaths 两个 cell 的地址为:

 // indexPath [0, 0]
 // indexPath [0, 1]

存在诡异的地方:

  1. 在 reloadRowsAtIndexPaths 是 indexPath [0, 0] 的 cell 被重新创建了。
  2. indexPath [0, 1] cell 地址等于 indexPath [0, 0] cell 的地址。

因为以上两个问题,导致在刷新第二行 cell 时出现了原本在第一行的默认头像,如果用 reload 刷新整个 TableView 则不会有这样的问题。

当然,最好的方式是自定义头像 cell,为方便起见使用这种方式,发现了这个问题。如果有大神知道原理,请指点一二 。

参考 Demo 地址 UITableViewBug

你可能感兴趣的:(慎用 reloadRowsAtIndexPaths)