我正在创建细胞时进行一些繁重的计算。我试图弄清楚保持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)
表现非常糟糕。你能解释一下为什么吗?
答案 0 :(得分:6)
您的实施存在根本缺陷。您将对单元格的引用引入在后台执行的块中。当您滚动并从屏幕上取下单元格时,它们将被放入重用池中。当新细胞进入屏幕时,它们会从这个池中被拉出来。块完成后,单元格可能不再位于同一行中。为了说明这一点,我将此声明放在工作块的开头:
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上有两个线程。每个核心一个线程。这使得调度更加简单,并为线程分配更准确的优先级。最终结果是主线程有足够的时间执行。
答案 1 :(得分:3)
这不是一个好方法。你通过将计算工作推入视图控制器来破坏MVC。您应该将数据保存在模型对象中并在那里管理计算。然后,表格视图应该只在数据发生变化时使用reloadRowsAtIndexPaths:withRowAnimation:
显示模型中的当前值。 KVO是确定数据何时发生变化的一种合理方式。
如果你想在计算中保持懒惰,那么你可以调用像recalculateIfNeeded
这样的模型来让模型知道有人(在这种情况下是表格视图)想要一个新值。但是,如果输入数据实际没有改变,则不应重新计算。该模型可以跟踪数据是否发生了变化。