CoreData从入门到精通五(结合tableview)

我们知道 CoreData 里存储的是具有相同结构的一系列数据的集合,TableView 正好是用列表来展示一系列具有相同结构的数据集合的。所以,要是 CoreData 和 TableView 能结合起来,CoreData 查询出来的数据能同步地显示在 TableView 上,更好一点就是 CoreData 里的改动也能同步到 TableView 上,那就再好不过了。可喜的是,确实有这样一个 API,那就是 NSFetchedResultsController,相信不少人对这个东西都不陌生,因为用 Xcode 创建带有 CoreData 的 Master-Detail 模板工程时,就是用这个接口来实现的。这篇文章也主要是围绕着模板工程中的代码进行介绍,如果你对这块比较熟悉的话,不妨直接去看模板里的代码;如果你是第一次听说这个 API,不妨继续看下去,相信会对你有帮助的。

直接上代码:

@interface ViewController ()<UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate>

@property (strong, nonatomic) NSManagedObjectContext *context;
@property (strong, nonatomic) UITableView *tableView;
@property (strong, nonatomic) NSFetchedResultsController *fetchedResultController;

@end

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.title = @"Navigation";
    UIBarButtonItem *leftItem = [[UIBarButtonItem alloc] initWithTitle:@"添加" style:UIBarButtonItemStyleDone target:self action:@selector(addStudent)];
    self.navigationItem.leftBarButtonItem = leftItem;
    
    //APPdelegate操作
    AppDelegate *delegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    self.context = delegate.persistentContainer.viewContext;
    
    [self setUpTableView];
}

- (void)setUpTableView {
    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    self.tableView.backgroundColor = [UIColor whiteColor];
    self.tableView.tableFooterView = [UIView new];
    [self.view addSubview:self.tableView];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    // sections 是一个 NSFetchedResultsSectionInfo 协议类型的数组,保存着所有 section 的信息
    return self.fetchedResultController.sections.count;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // sectionInfo 里的 numberOfObjects 属性表示对应 section 里的结果数量
    id<NSFetchedResultsSectionInfo> sectionInfo = self.fetchedResultController.sections[section];
    return sectionInfo.numberOfObjects;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"cell"];
    }
    // 通过这个方法可以直接获取到对应 indexPath 的实体类对象
    Student *student = [self.fetchedResultController objectAtIndexPath:indexPath];
    cell.textLabel.text = student.name;
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%d",student.age];
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 45;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    return 0.00000001;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    return 0.00000001;
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    return nil;
}

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
    return nil;
}

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        Student *student = [self.fetchedResultController objectAtIndexPath:indexPath];
        [self.context deleteObject:student];
        [self.context save:nil];
    }
}

//NSFetchedResultsController 的初始化
- (NSFetchedResultsController <Student *>*)fetchedResultController {
    //创建 fetchedResultsController 需要指定一个 fetchRequest,这很好理解,因为 fetchedResultsController 也需要查询 CoreData 数据库里的数据,需要注意的是,指定的这个 fetchRequest 必须要设置 sortDescriptors 也就是排序规则这个属性,不设置直接运行的话,程序是会直接崩溃的,这是因为 fetchedResultsController 需要根据这个排序规则来规定数据该以什么顺序显示到 tableView 上,而且这个 fetchRequest 指定之后就不可以再修改了
    if (!_fetchedResultController) {
        NSFetchRequest *fetchRequest = [Student fetchRequest];
        fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES]];
        //fetchLimit 之前讲过是指定获取数据的上限数量,而 fetchBatchSize 是分批查询的数据量大小
        fetchRequest.fetchBatchSize = 10;
        fetchRequest.fetchLimit = 100;
        //context 就是上下文的对象;sectionNameKeyPath 可以指定一个 keypath 来为 tableView 生成不同的 section,指定成 nil 的话,就只生成一个 section; cacheName 用来指定一个缓存的名字,加载好的数据会缓存到这样一个私有的文件夹里,这样可以避免过多的从 CoreData 数据库里查询以及计算的操作
        _fetchedResultController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.context sectionNameKeyPath:@"age" cacheName:@"StudentTable"];
        _fetchedResultController.delegate = self;
        //指定的泛型 Student 就是 fetchRequest 查询的数据类型。这些都配置之后调用 performFetch: 方法就可以执行查询操作了。返回的数据保存在 fetchedResultsController 的 fetchedObjects 属性里,不过我们一般不会直接用到它
        [_fetchedResultController performFetch:nil];
    }
    return _fetchedResultController;
}

上一步里我们实现了把 fetchedResultsController 里的数据绑定到 TableView 上,但还没完成同步更新的实现,例如 CoreData 数据库里新插入了数据,TableView 这时也可以自动更新。实现这个功能,只需要实现 fetchedResultsController 的 delegate 就可以了。

NSFetchedResultsControllerDelegate 里有一个 NSFetchedResultsChangeType 枚举类型,其中的四个成员分别对应 CoreData 里的增删改查:

typedef NS_ENUM(NSUInteger, NSFetchedResultsChangeType) {
    NSFetchedResultsChangeInsert = 1,
    NSFetchedResultsChangeDelete = 2,
    NSFetchedResultsChangeMove = 3,
    NSFetchedResultsChangeUpdate = 4
} 

5个代理方法:

// 对应 indexPath 的数据发生变化时会回调这个方法
@optional
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(nullable NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(nullable NSIndexPath *)newIndexPath;

 // section 发生变化时会回调这个方法
@optional
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type;

 // 数据内容将要发生变化时会回调
@optional
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;

 // 数据内容发生变化之后会回调
@optional
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller;

 // 返回对应 section 的标题
@optional
- (nullable NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName;

@end

想要实现 tableView 的数据同步更新可以按下面的代码来实现这几个 delegate 方法:

\#pragma mark --- NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    // 在这里调用 beginUpdates 通知 tableView 开始更新,注意要和 endUpdates 联用
    [self.tableView beginUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    // beginUpdates 之后,这个方法会调用,根据不同类型,来对tableView进行操作,注意什么时候该用 indexPath,什么时候用 newIndexPath.
    switch (type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeMove:
            [self.tableView moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
            break;
        case NSFetchedResultsChangeUpdate:
        {
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
            Student *stu = anObject;
            cell.textLabel.text = stu.name;
            cell.detailTextLabel.text = [NSString stringWithFormat:@"%d",stu.age];
            break;
        }
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;
    }
    
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    // 更新完后会回调这里,调用 tableView 的 endUpdates.
    [self.tableView endUpdates];
}

- (void)addStudent {
    NSString *name = [NSString stringWithFormat:@"student-%u", arc4random_uniform(9999)];
    int16_t age = (int16_t)arc4random_uniform(10) + 10;
    Student *student = [NSEntityDescription insertNewObjectForEntityForName:@"Student" inManagedObjectContext:self.context];
    student.name = name;
    student.age = age;
    NSError *error;
    [self.context save:&error];
}

over!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容