我一直在开发一个应用程序,该应用程序涉及对单元格内容进行一些相当繁重但可变的计算。我以几种不同的方式解决了这个问题,但结果很糟糕。我终于找到了一个好的解决方案,但我决定将其发布以供讨论和批评。
我的特殊问题是我有一个可以显示 1 到 365 个单元格的日历,每个单元格显示八个必须即时计算的值。(一些有限的值缓存是可能的,但我很快发现数据使完全缓存变得不可能。)在我尝试的最坏情况下,完整的计算链最多需要 30 秒,但是随着更复杂的数据集,这可能会增加. 显然,在创建单元格时简单地计算值会产生不可接受的延迟,更糟糕的是,会产生无响应的界面。(当然,下面的代码已经被简化了。)
- (UICollectionViewCell*)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
switch (_typeOfLayout) {
case BACalendarLayoutTypeDay:
{
BAPeriod *period = [BAPeriod newWithSingular:_date];
BACalendarViewCell * cell = (BACalendarViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:BACalendarCellKind forIndexPath:indexPath];
cell.period = period;
BAPeriod * periodToDate = [[period preceding] union:period];
cell.value1 = [_modelInterface totalValue1:periodToDate];
cell.value2 = [_modelInterface totalValue2:periodToDate];
...
return cell;
}
case BACalendarLayoutTypeWeek:
...
}
}
我的第一个解决方案涉及将计算推迟到队列中,但这需要非常仔细地同步计算链,这导致单元格显示然后更新我想要的方式。然而,这也导致了随机死锁和结果显示的更长延迟。也有可能在计算值时,单元格可能已被删除。
- (UICollectionViewCell*)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
switch (_typeOfLayout) {
case BACalendarLayoutTypeDay:
{
BAPeriod *period = [BAPeriod newWithSingular:_date];
BACalendarViewCell * cell = (BACalendarViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:BACalendarCellKind forIndexPath:indexPath];
cell.period = period;
BAPeriod * periodToDate = [[period preceding] union:period];
cell.valuesAvailableNow = false;
dispatch_async(_queue, ^{
cell.value1 = [_modelInterface totalValue1:periodToDate];
cell.value2 = [_modelInterface totalValue2:periodToDate];
...
cell.valuesAvailableNow = true;
[cell needsDisplay];
}
return cell;
}
case BACalendarLayoutTypeWeek:
...
}
}
我的下一个解决方案是忘记同步并在主队列上连续执行所有计算,但使用我创建的队列来管理计算。我还开始检查单元格是否仍然可用,以防用户快速切换到另一个视图。结果是时间有所改善,但界面在显示后变得无响应,用计算值更新单元格需要很长时间,并且由于某种原因有时无法更新单元格。
- (UICollectionViewCell*)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
switch (_typeOfLayout) {
case BACalendarLayoutTypeDay:
{
BAPeriod *period = [BAPeriod newWithSingular:_date];
BACalendarViewCell * cell = (BACalendarViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:BACalendarCellKind forIndexPath:indexPath];
cell.period = period;
BAPeriod * periodToDate = [[period preceding] union:period];
cell.valuesAvailableNow = false;
dispatch_async(_queue, ^{
dispatch_group_t group = dispatch_group_create();
dispatch_group_asynch(group, dispatch_get_main_queue(), ^{
if ([self cellStillActive:cell]) {
cell.value1 = [_modelInterface totalValue1:periodToDate];
}
});
dispatch_group_asynch(group, dispatch_get_main_queue(), ^{
if ([self cellStillActive:cell]) {
cell.value2 = [_modelInterface totalValue2:periodToDate];
}
});
...
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
cell.valuesAvailableNow = true;
[cell needsDisplay];
}
return cell;
}
case BACalendarLayoutTypeWeek:
...
}
}
我意识到问题在于所有的计算都被转储到主队列中以在一个实体块中执行,并且在它们之后添加了新事件。最后一个效果很好的解决方案是在主线程中添加同步执行计算,而不是在前一个计算完成之前添加新的计算。这允许在计算之间异步添加系统事件。结果是良好的界面响应能力和出色的计算能力。
- (UICollectionViewCell*)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
switch (_typeOfLayout) {
case BACalendarLayoutTypeDay:
{
BAPeriod *period = [BAPeriod newWithSingular:_date];
BACalendarViewCell * cell = (BACalendarViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:BACalendarCellKind forIndexPath:indexPath];
cell.period = period;
BAPeriod * periodToDate = [[period preceding] union:period];
cell.valuesAvailableNow = false;
dispatch_async(_queue, ^{
if ([self cellStillActive:cell]) {
dispatch_synch(group, dispatch_get_main_queue(), ^{
cell.value1 = [_modelInterface totalValue1:periodToDate];
});
}
if ([self cellStillActive:cell]) {
dispatch_synch(group, dispatch_get_main_queue(), ^{
cell.value2 = [_modelInterface totalValue2:periodToDate];
});
}
...
cell.valuesAvailableNow = true;
[cell needsDisplay];
}
return cell;
}
case BACalendarLayoutTypeWeek:
...
}
}
所以……意见?其他解决方案?