1

NSFetchedResultsController是一个非常好用且强大的数据库绑定类,用来处理CoreDataUIView的数据绑定非常便捷。

例如官方例子中,实用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,并不可以在幕后之行insertdelete操作,会抛一个莫名其秒的异常。所谓幕后,就是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方法存在的意义。

所以还是需要用performBatchUpdatesblock住批量修改UI的代码,才能不报错。
而这个方法,必须在主线程执行。

dispatch_async(dispatch_get_main_queue(), ^{
        [self.collectionView performBatchUpdates:^{
        
        这里处理
}];
});

愿好运


秋刀生鱼片
2.1k 声望82 粉丝

独立游戏开发者