【问题标题】:GCD implementation on a UITableViewUITableView 上的 GCD 实现
【发布时间】:2012-04-11 17:24:20
【问题描述】:

在创建单元格时,我正在做一些繁重的计算。我试图找出保持 UITableView 流畅的最佳方法,但在同一类型的背景下进行计算(保持 UI 线程没有太多处理)。

仅出于测试目的,我将其用作我的繁重计算方法:

+(NSString*)bigCalculation
{
    int finalValue=0;
    int j=0;
    int i=0;

    for (i=0; i<1000; i++) {
        for (j=0; j<10000000; j++) {
            j++;
        }
        finalValue+=j/100*i;
    }

    return [NSString stringWithFormat:@"%d",finalValue];
}

在 cellForRowAtIndexPath 内部,我只做以下事情:

- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *identifier=@"identifier";

    UITableViewCell *cell=nil;
    cell=[aTableView dequeueReusableCellWithIdentifier:identifier];
    if(!cell)
    {
        cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:identifier];
    }

    NSString *text=[dataSource objectForKey:[[dataSource allKeys] objectAtIndex:indexPath.row]];


    dispatch_queue_t a_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
;
    dispatch_async(a_queue, ^{

        NSString *subtitle=[CalculationEngine bigCalculation];
        dispatch_async(dispatch_get_main_queue(), ^{
            [[cell detailTextLabel] setText:subtitle];
            dispatch_release(a_queue); 
        });
    });


    [[cell textLabel] setText:text];
    return cell;
}

目前我有 UITableView 流体,而在后台一切正常。所以我的问题是:

1)这是实现我想要的最佳方式吗?KVO 也可以作为答案吗?

2) 在此之前:

dispatch_queue_t a_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

我在做:

dispatch_queue_create("com.mydomain.app.newimagesinbackground", DISPATCH_QUEUE_SERIAL)

而且性能很差。你能解释一下为什么吗?

【问题讨论】:

  • 您是为每个单元创建一个新的串行队列还是在它们之间共享一个。我假设您了解将所有任务安排在同一个串行队列上的含义。
  • 另外,由于您不拥有全局队列,因此您没有义务释放它。 dispatch_release(a_queue) 将被忽略。
  • 此外,由于该块包含对队列的引用,您可以在提交工作后立即释放它。
  • 嗨 pingbat,现在我知道了。我确实很难理解。 :P(根据 Instruments,296 个线程正在运行)
  • 因此,我怀疑两者的总体执行时间都差不多。通过使用并发队列,您将更快地返回第一个结果,因为调度程序将对一次执行的块数设置限制。使用许多串行队列可能会导致所有块同时运行(如果您滚动整个 TableView)。这会让您等待很长时间,然后突然间所有结果都会在大致相同的时间出现。这听起来像你看到的行为吗?

标签: iphone ios multithreading concurrency grand-central-dispatch


【解决方案1】:

首先

您的实现存在根本缺陷。您将对单元格的引用放入在后台执行的块中。当您滚动并从屏幕上取下单元格时,它们将被放入重用池中。当新的细胞出现在屏幕上时,它们就会从这个池中被拉出来。当您的块完成时,单元格可能不再位于同一行中。为了说明这一点,我将以下语句放在工作块的开头:

NSLog(@"my Row is: %d myCell is: %x", indexPath.row, (unsigned int)cell);

导致:

我的行是:0 myCell 是:16fd20
我的行是:13 myCell 是:16fd20
我的行是:24 myCell 是:16fd20
我的行是:35 myCell 是:16fd20
我的行是:44 myCell 是:16fd20
我的行是:56 myCell 是:16fd20
我的行是:66 myCell 是:16fd20

所以在这里您可以看到 7 个块,它们都在计算不同的行,但都指向同一个单元格。
如果用户在块完成时滚动,则会更新错误的单元格。

您还希望在更新单元格时使用dispatch_sync(dispatch_get_main_queue(),0) 以确保立即对其进行处理。

KVO 是解决这个问题的一种方法,导致..


你的第一个问题:

KVO 将是实现此目的的好方法。正如 Rob Napier 提到的,您应该为列表中的每个项目都有一个单独的模型对象。为了管理 tableView 中单元格的更新,您希望继承 UITableViewCell。然后,单元格订阅模型对象以获取通知,并且可以在收到通知时自行更新。如果单元格模型对象发生更改,您只需退出旧模型对象的通知并订阅新的通知。

这应该保证您不会像当前代码那样在单元格中显示不准确的信息。

注意事项: KVO 在设置新值的同一线程上发送通知。这意味着您需要确保您要么在主线程上设置新值,要么在 observeValueForKeyPath: 方法中在主线程上调度一个块。


你的第二个问题:

这样排队的原因:

dispatch_queue_create("com.mydomain.app.newimagesinbackground", DISPATCH_QUEUE_SERIAL);

太慢了,以至于您为每个计划的块生成一个串行队列。 这些队列将同时执行。由于您只有少量内核,因此队列的执行时间越来越少。主队列(运行 UI)只是另一个队列,它必须执行的时间也越来越少,并且更小。

在我的测试中,我发现每个队列都有一个线程。与单元格一样多的线程。

使用全局并发队列:

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

允许系统管理一次执行的块数。这样做的最终结果是,你有一个队列,而不是数百个队列和数百个线程,而在我的 iPhone 4s 上,有两个线程。每个核心一个线程。这使得调度更简单,并为线程分配更准确的优先级。最终结果是主线程有足够的时间执行。

【讨论】:

  • 感谢您的解释。 :)
  • 我刚刚遇到了这个问题,因为我正在使用来自我的模型的通知来尝试刷新我的 UITableView UI。由于显而易见的原因,当当前没有显示与刚刚更改的模型对象匹配的单元格时,我遇到了崩溃。我已经实施了 KVO 策略,这似乎对我的初始测试产生了很大影响。当UITableViewCell不再出现在屏幕上时,当它被放置在重用队列中时,它如何移除观察者?我想这很重要,我已经在 dealloc 中放置了代码,以便在单元格被破坏时执行此操作。
  • CMVR:有一种“prepareForReuse”方法可以用于此目的。更多详情here
  • prepareForReuse: 在 dequeReusableCellWithIdentifier: 之前被调用。这可能就是你想要的。
【解决方案2】:

这不是一个好方法。您通过将计算工作推入视图控制器来破坏 MVC。您应该将数据保存在模型对象中并在那里管理计算。然后表格视图应该只显示模型中的当前值,当数据更改时使用reloadRowsAtIndexPaths:withRowAnimation:。 KVO 是确定数据何时更改的一种合理方法。

如果您想懒惰地进行计算,那么您可以像recalculateIfNeeded 这样调用模型,让模型知道有人(在本例中为表格视图)想要一个新值。但是,如果输入数据实际上没有更改,则不应重新计算。该模型用于跟踪数据是否已更改。

【讨论】:

  • 我的一种方法是在每个 UITableViewCell 的“后面”有一个 NSObject,然后将 KVO 添加到该对象。该对象将负责获取该特定单元格的数据。计算完成后,我会通知单元格。你怎么看?
  • 正确。您描述的 NSObject 听起来很像模型对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多