NSFetchedResultsController
是一个非常好用且强大的数据库绑定类,用来处理CoreData
和UIView
的数据绑定非常便捷。
例如官方例子中,实用NSFetchedResultsController
绑定UITableView
,完成绑定后,开发者只要专注处理数据就好,UI会根据数据变化自动更新。
并且NSFetchedResultsController
还提供了缓存功能,大大提高了大数据量的CoreData
检索效率。
然而这么好的东西,和UICollectionView
并不能配合的非常默契。
两个原因:
坑一
UICollectionView
不再有UITableView
的-(void)beginUpdates
和`-(void)endUpdates`的方法,但却提供了一个令人尴尬的批量处理Cell的外包围方法
- (void)performBatchUpdates:(void (^ __nullable)(void))updates completion:(void (^ __nullable)(BOOL finished))completion;
这个方法把操作包含到了block中执行,但是NSFetchedResultsController
的UI改变通知回调是分四个回调方法完成的。所以我被迫使用了NSBlockOperation
,把中间回调进行的操作包装到block中,再存到数组中,统一在
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
中执行。但在当前页面删除cell时候还是会报错。尴尬的我只好去掉了performBatchUpdates
外包围,直接执行block,这次看起来没有问题了。
包装block的方法是这样
@property (nonatomic, strong) NSMutableArray* op;
[self.op addObject:[NSBlockOperation blockOperationWithBlock:^{
[collectionView insertItemsAtIndexPaths:@[newIndexPath]];
}]];
执行block的方法是这样
[self.op enumerateObjectsUsingBlock:^(NSBlockOperation* _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[[NSOperationQueue mainQueue] addOperation:obj];
}];
坑二
第二个坑,是UICollectionView,并不可以在幕后之行insert
和delete
操作,会抛一个莫名其秒的异常。所谓幕后,就是UICollectionView的VC不是当前显示在最前端的VC,这种情况也是很常见的。
所以这个时候,要直接调用[collectionView reloadData]
方法。
像这样
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
if (self.onTop) {
[self.op enumerateObjectsUsingBlock:^(NSBlockOperation* _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[[NSOperationQueue mainQueue] addOperation:obj];
}];
}
else
{
[self.collectionView reloadData];
}
}
然而这一切还是有问题,问题就出在,数据源上的数据量和执行insertItemsAtIndexPaths方法时的数据量不同。
这就是performBatchUpdates
方法存在的意义。
所以还是需要用performBatchUpdates
block住批量修改UI的代码,才能不报错。
而这个方法,必须在主线程执行。
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView performBatchUpdates:^{
这里处理
}];
});
愿好运