7

设置:我有一个UICollectionViewUITableView由一个简单的数组数据源支持。我在控制器中保留了该数据源的副本。

现在,我收到来自系统的通知,说有新数据可用。我得到一个新数组,其中可能已添加、删除和更改了位置。

所以现在我有两个数据对象:

  • 与 UI 当前显示的内容同步的上一个数组
  • 已添加、删除、移动项目的新数组

为了使 UI 与新数组同步,我需要生成一堆 UI 调用。在 的情况下UICollectionView,那些是

- (void)insertItemsAtIndexPaths:(NSArray *)indexPaths
- (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath
- (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths

并且有一组类似的方法用于UITableView.

我特别不想重新加载整个表格,因为这比只处理几件物品要贵。

所以,问题是:给定以前的和新的数据源数组,我如何生成正确的 UI 调用集,我什么时候“换掉”我的旧数据源为新的?

4

4 回答 4

8

我认为这在很大程度上等同于差异/补丁问题,其目的是找到一个文本文件和另一个文本文件之间的最小更改数量,然后应用这些更改。在这种情况下,实现定义了操作:

  • 添加或插入
  • 删除
  • 改变

……但不省略move的原因对我来说并不是很明显,但我强烈怀疑包括move将需要非常昂贵的计算才能找到最佳运动。

因此,如果我们将操作限制在上面列出的范围内,差异文件比较算法或其派生算法中描述的 Hunt-McIlroy 算法将找到一组接近最优的更改。

您的问题与经典diff / patch之间的区别在于您有一个二维表,而diff / patch处理一维项目集(文本行)。将 2-D 问题转换为 1-D 问题的最佳方法将取决于数据表中倾向于进行的更改的特定特征。

例如,如果表格是n行 x m列,并且更改倾向于按行分组,或者将行作为一个整体插入或删除,那么您可能最好将表格视为文本文件并执行逐行区分。或者,如果更改倾向于按列分组或插入或删除列,则可以逐列进行差异。如果更改包括插入或删除单个单元格(结果导致后续单元格向右或向左移动),您可以将表格视为表格中的每个单元格位于文本文件的单独行上,使表格线性化以行优先或列优先的顺序。

但是,在不知道这方面问题的细节的情况下,我倾向于避免过早的优化。因此,我倾向于首先在m < n时逐行实施 Hunt-McIlroy 算法,如果n < m则逐列实施,然后在确定使用中的应用程序一段时间之前对其进行分析更复杂的算法版本或将您的问题映射到 Hunt-McIlroy 解决方案的替代映射是必要的。

可以在 stackoverflow上找到对各种diff算法的很好的讨论。

于 2013-01-11T22:29:13.780 回答
4

我开发了一个应用程序来处理我能够解决的非常相似的问题。我期待一个复杂的解决方案,但它真的很简单。以下是您可以解决的方法:

1)创建一个新数组,您将在其中接收新项目并将其命名为“moreItems”,当然也可以合成它。

@property (nonatomic, readonly) NSMutableArray *moreItems;

2) 在与您的 TableView 或 CollectionView 链接的 ViewController 的 viewDidLoad 中,分配/初始化此数组:

moreItems = [[NSMutableArray alloc] init];

3)现在您需要添加现有数组,让我们说它是“项目”到您刚刚创建的名为“moreItems”的新数组中。您可以使用“addObjectsFromArray”来做到这一点

[moreItems addObjectsFromArray:[channel items]];

'channel items' 包含它之前收到的对象,它将这些项目添加到新创建的名为 'moreItems' 的数组中。我假设您正在从某个 Web 服务收集数据,因此您可以在 connectionDidFinsihLoading 中实现此步骤。

4) 现在更改 TableView/CollectionView 的数据源并将其替换为新数组“moreItems”

5) 下一个问题是您不想重新加载整个 tableview 并且想要处理新项目。要拥有此功能,您需要持久化项目。您可以使用任何您喜欢的归档或核心数据。

6) 已经获取的项目,你将不得不坚持他们让我们说存档。并在用户打开应用程序时在 tableview 中显示它们,同时它会抓取更多在 Web 服务上更新的项目。所以首先立即显示持久化的项目,然后处理新项目。

7)您需要寻找一些独特的对象,在我的情况下它是“链接”,因为每个项目都有不同的链接,我可以在此基础上对它们进行排序和处理。你还需要使用

- (BOOL) isEqual:(id)object

比较 Web 服务上的链接和 tableview 中已经存在的链接。此步骤是必要的,因为这样您的应用程序将不会添加带有已在 tableview 中的链接的项目。

8)如果每个项目都有某个日期,您可以使用该日期对它们进行排序并使用“sortUsingComparator”将新的显示在顶部

9)您还需要使用“[[self tableView] insertRowsAtIndexPath:rows withRowAnimation:UITableViewRowAnimationTop”来向用户显示新项目已添加到早期项目之上。

希望这可以帮助。

于 2013-01-16T22:31:01.753 回答
3

我特别不想重新加载整个表格,因为这比只处理几件物品要贵。

你确定这是真的吗?除非您有病态的情况,即屏幕上的每个单元格都可见并且有数百个,否则我很难相信“有效”方式会比“蛮力”方式更快。

调用reloadData只会要求您的委托更新那些最终将在屏幕上可见的单元格,它不会导致整个表格视图重新创建其每个单元格。

如果您正在实施一些改变计算尺寸复杂性的方法(例如tableView:heightForRowAtIndexPath),那么使用“高效”方式将不会获得任何好处,因为表格视图无论如何都必须重新计算其所有高度。

我不确定您正在寻找的解决方案是否能解决问题,但我们只是希望我错了。

于 2013-01-17T21:31:40.127 回答
2

如果您的数据源已经有差异信息,UITableView reloadRowsAtIndexPaths:withRowAnimation:将降低性能密集型:

于 2013-01-17T17:55:47.607 回答