防止 UICollectionView 崩溃

Preventing crash in UICollectionView

我们的应用程序有 UICollectionView 并且其 dataSource 词典会定期更新。我们永远不知道下一次更新何时会发生。用户点击按钮后可以调用集合视图重新加载方法,也可以在网络请求成功后异步发生。鉴于上述信息,我们有可能在重新加载集合视图并同时更新其数据源时出现竞争条件。我们甚至记录了以下崩溃,我们相信它是由于上述竞争条件而发生的。崩溃信息:

*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'

导致崩溃的方法:collectionViewLayout sizeForItemAtIndexPath:.

此方法根据 sectionWithProducts 中的项目数计算集合视图部分的高度。它崩溃是因为 dataSource 计数小于 indexPath.row。导致崩溃的行:

NSArray *sectionWithProducts = self.dataSource[indexPath.row];

崩溃发生前调用了以下行:

[self.collectionView setCollectionViewLayout:[self flowLayout] animated:NO];
[self.collectionView scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:NO];
[self.collectionView reloadData];

为了防止这种情况,我们决定将唯一一行更新数据源的代码放到主线程中。

// Always run on main thread in hope to prevent NSRangeException. 
// Reloading data should happen only sequential.
dispatch_async(dispatch_get_main_queue(), ^(void) {
    self.dataSource = newValue;
});

我们的代码中有很多 [self.collectionView reloadData]。在主线程上 运行 它们也值得吗?它发生得很快,所以它不应该长时间阻塞 UI。

UICollectionViewDelegateFlowLayout 使用 indexPath 属性 的委托方法是否总是在后台队列上调用?

首先,所有UIKIT方法都应该在主线程上调用,包括reloadData。这虽然不会解决你的崩溃。其次,在您的代码中的某处存在竞争条件,您正在调用 reloadData 并同时更改数据源,您需要找出它发生的位置。这是我在没有看到实际代码的情况下可以说的。