UITableView上的GCD实现

Rui*_*res 1 iphone concurrency multithreading grand-central-dispatch ios

当我创建单元格时,我正在进行一些繁重的计算.我试图弄清楚保持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];
}
Run Code Online (Sandbox Code Playgroud)

在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;
}
Run Code Online (Sandbox Code Playgroud)

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

1)这是实现我想要的最佳方式,KVO也能成为答案吗?

2)在此之前:

dispatch_queue_t a_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
Run Code Online (Sandbox Code Playgroud)

我在做:

dispatch_queue_create("com.mydomain.app.newimagesinbackground", DISPATCH_QUEUE_SERIAL)
Run Code Online (Sandbox Code Playgroud)

而且表现非常糟糕.你能解释一下为什么吗?

mjm*_*vis 6

首先

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

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

这导致:

我的行是:0 myCell是:16fd20
my row is:13 myCell is:16fd20
my row is:24 myCell is:16fd20
my row is:35 myCell is:16fd20
my row is:44 myCell is:16fd20
my row is: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);
Run Code Online (Sandbox Code Playgroud)

是如此之慢,以至于您为每个预定的块生成一个串行队列.这些队列将全部同时执行.既然你只有一小芯数,队列有时间来执行自己的小片.主要队列(它运行UI)是另一个队列的时间它具有执行量也较小,小.

在我的测试中,我发现每个队列有一个线程.像细胞一样多的线程.

使用全局并发队列:

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
Run Code Online (Sandbox Code Playgroud)

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