iOS上的TableView异步刷新

时间:2012-01-16 15:11:36

标签: ios uitableview

我有一个使用此方法填充的简单TableViewController:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        NSInteger row = [indexPath row];
        cell.textLabel.text = [[NSString alloc] initWithFormat:@"%d", row]; 

        dispatch_async(dispatch_get_main_queue(), ^{

            [cell setNeedsLayout];

        });

    }); 

    return cell;
}

在第一次加载时,数字被正确显示。 当我收看视图时,数字随机显示(然后它们被正确排序)。

为什么,滚动,数字没有排序?

enter image description here

2 个答案:

答案 0 :(得分:3)

UIKit不是线程安全的。您无法在另一个线程上设置UIImageView的图像,同样,您也无法在另一个线程上设置UILabel的文本。试试这个:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSInteger row = [indexPath row];
    [cell.textLabel  performSelectorOnMainThread:@selector(setText:) withObject:[NSString stringWithFormat:@"%d", row] waitUntilDone:YES];
}); 

但就个人而言,我不明白你为什么要使用派遣。从整数创建字符串的开销很小,除非幕后处理的内容比此处显示的要多。

您可以改为使用此代替调度调用:

 NSInteger row = [indexPath row];
 cell.textLabel.text = [NSString stringWithFormat:@"%i", row];

答案 1 :(得分:3)

我想通过阅读你正在尝试做的事情,你会以错误的方式稍微探讨一下。您不应该在cellForRowAtIndexPath中执行异步操作,因为您无法确定稍后要更新的单元格是否正确。

考虑你的代码:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }

    ...

    return cell;
}

所以你要创建一个单元并返回它。它可能是重用队列中的一个或新的队列。到现在为止还挺好。但是你添加了这段代码:

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSInteger row = [indexPath row];
        cell.textLabel.text = [[NSString alloc] initWithFormat:@"%d", row]; 
        dispatch_async(dispatch_get_main_queue(), ^{
            [cell setNeedsLayout];
        });
    }); 

所以你现在派遣到后台队列来设置那个单元格上的东西。但是如果发生这些事件会发生什么:

  1. 表格要求提供一个单元格。

  2. 创建新单元格,单元格A,调度完成后再设置。

  3. 用户滚动和单元格A从视图中删除并放入重用队列。

  4. 表格要求提供一个单元格。

  5. 从重用池返回Cell A,并将另一个dispatch放入队列。

  6. 首次发送完成,在单元格A上设置内容。

  7. 这不是你想要的,因为现在单元格A上有旧数据。当然,如果你的队列是串行的,你可能可以逃脱它,因为第二个调度将始终在第一个之后完成,但是a)这不好,因为无论如何都会看到过时的数据而b)你的队列可能是并行队列反正。

    那么,你问什么是正确的解决方案?好吧,我通常采用的方法是定制单元格。听起来你想让单元格加载一个图像,我假设你想从网络加载该图像?所以我这样做的方法是在其上有一个loadImage方法的自定义单元格,当表格视图停止滚动并且没有减速,或者表格视图停止减速时,在每个可见单元格上调用该单元格。 (请参阅UIScrollViewDelegate方法,了解我的意思。)

    然后在loadImage方法中,我触发HTTP请求以获取图像并等待它返回并将我的自定义单元格中的UIImageView设置为返回的图像。关键的额外一点是我在自定义单元格上有一个setter来设置对象/ URL /单元格显示的内容,它提供了有关要下载哪个图像的信息。然后,当单元格获得新URL请求时,它会取消旧URL并在下一次loadImage调用中启动新URL。

    希望这是有道理的!我实际上做的略有不同,但这是一个简单的描述,因为我可以给出一般的想法。遗憾的是,我没有任何代码可以显示,但请提出任何问题,我会尝试再填写一些问题。