前两天项目里遇到一个不太常见的collectionView的crash(标题起这么长是为了方便遇到同样问题的朋友能够快速检索到)
UICollectionView received layout attributes for a cell with an index path that does not exist:
<NSIndexPath: 0x80b1dc933cf8f0de> {length = 2, path = 0 - 4}
简单翻译一下:UICollectionView接收到了下标不存在的cell的布局属性。
结合项目具体crash的场景发现:collectionView reloadData时,数据源已经变了,但是cell的layout attributes还没有更新,也就是collectionViewLayout出了故障。
stackoverflow上看到很多人遇到了这个问题,解决办法五花八门,我试了一圈在这个场景下基本没用。
说的最多的是在各个地方调用这句让布局失效的代码:
[self.collectionView.collectionViewLayout invalidateLayout];
这句代码从理论上讲应该是可以起作用的,然而我试了没什么卵用,后来我冷静下来思考了一下,既然是要让现有布局无效,不如我直接重新alloc一个collectionViewLayout好了。。
即每次刷新数据之前调一下这个方法
- (void)refreshFlowLayout
{
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc]init];
self.vipCollectionView.collectionViewLayout = flowLayout;
}
就解决了。。
collectionViewLayout为什么会发生故障,这个暂时还没有头绪,stackoverflow上也没人提到过,下周过来再研究研究。
周一过来,又回头看这个问题,恰巧同事lkk跟我说:“你是不是改了啥,某处在模拟器上一直崩溃”。
慌的一批,明明我的真机是不崩的,但是模拟器崩溃也是不能忍的,我也懒得管是否在部分版本系统上会崩溃了,直接回滚代码,重新寻找新的解决方案。。
重新翻看stackoverflow的文章,一条一条的看,终于看到一条让我有所启发的comment:
这边的意思是说当数据源发生变化的时候,先改变了contentInset再reloadData的话,就会crash,反之先reloadData再改变contentInset则是安全的!
原因是改变contentInset会触发collectionView的布局更新,如果数据源改变时先改变contentInset(即先更新布局,再reloadData),layout attributes的数据和新的数据源不对应,发生崩溃也就不奇怪了~
仔细看了下崩溃的相关代码,发现确实有这个骚操作😅
于是把改变contentInset的方法放在reloadData后面执行就好了:
小结:第一种解决方案是在没有找到问题真正的根源时的无奈之举(周五晚上没吃晚饭的情况下,fix到实在饿的撑不住了的临时方案)。第二种方案则是刨根问底,寻找到问题的本源,从而对症下药。项目开发中,还是要尽可能的采用第二种思路去解决问题,否则项目的健壮性会越来越差。