美文网首页
Data Source 新特性:基于 Diffable 实现局部

Data Source 新特性:基于 Diffable 实现局部

作者: Mr_Sunkaixuan | 来源:发表于2020-12-09 20:46 被阅读0次

前言

平时开发中,经常用到UITableView 和 UICollectionView 是很常用的 UI 控件,在过去我们通常需要实现 Data Sources 来配置数据源,虽然在简单的业务中我们可以愉快的实现各种需求,可是一旦业务复杂起来,比如数据源实时的增删改,我们经常会一不小心就遇到 NSInternalInconsistencyException(Data Source 和当前 UI 状态不一致)等奇奇怪怪的异常。

本文参考以下文章

WWDC 2019 Session 220:Advances in UI Data Sources
Data Source 新特性:基于 Diffable 实现局部刷新

目前使用情况

现在大多数开发中遇到列表页,我们都会实现UITableView 的UITableViewDataSource方法

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

以上为必须实现的代理方法

考虑的问题

在我们平时开发中肯定会经常用到,对这些数据的处理,增、删、改、查 的一些数据操作。同时会经常衍生出一些让我们比较头疼的问题,我们在进行操作时,首先要计算操作的数据在数据源的位置信息,然后在刷新我们计算出来的indexpath,或者更加粗暴的将整个数据源刷新。(繁琐的计算,更有稍不甚者会导致闪退,还有消耗性能的全部刷新)

新的数据源Diffable Data Source 新的API

UITableViewDiffableDataSource继承自NSObject(开发的时候尽量创建一个自己的类,来继承自它,方便我们整体去修改一些东西,这代码习惯,应该大家都懂吧哈哈)
它是用来维护UITableView的数据源,Section 和 Item 遵循 IdentifierType,从而确保每条数据的唯一性。所以我们在增、删、改、查 的一些数据操作时,直接根据IdentifierType获取到要操作的数据就可以了。

直接贴出实践的代码,来供参考(很多使用注意点都写在注释里面了,仔细看看注释哈)

初始化UITableView和UITableViewDiffableDataSource

#pragma mark - setter--geeter
- (TestDiffableDataSource *)diffableDataSource{
    if (!_diffableDataSource) {
        
        //cellProvider相当于cellForRowAtIndexPath
        _diffableDataSource = [[TestDiffableDataSource alloc]initWithTableView:self.tableView cellProvider:^UITableViewCell * _Nullable(UITableView * _Nonnull tableView, NSIndexPath * _Nonnull indexPath, id _Nonnull data) {
            UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([UITableViewCell class])];
            if (!cell) {
                cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NSStringFromClass([UITableViewCell class])];
            }
            
            id test = [self->_diffableDataSource itemIdentifierForIndexPath:indexPath];
            
            NSLog(@"itemIdentifier: %@  indexPath: %@",test,indexPath);
            
            TestModel *model = (TestModel *)data;
            cell.textLabel.text = model.title;
            cell.detailTextLabel.text = model.subTitle;
            return cell;
        }];
    }
    return _diffableDataSource;
}
- (UITableView *)tableView{
    if (!_tableView) {
        _tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 88, self.view.frame.size.width, self.view.frame.size.height-88) style:UITableViewStylePlain];
        
        //把tableView的数据源设置成自己封装的类diffableDataSource
        _tableView.dataSource = self.diffableDataSource;
        
        //设置tableView为本VC,进行一些操作
        _tableView.delegate = self;
        
        //去掉tableView的尾部多余的下划线
        _tableView.tableFooterView = [[UIView alloc]init];
    }
    return _tableView;
}

有没有很意外的发现,cellForRowAtIndexPath方法在初始化UITableViewDiffableDataSource方法里实现了

接下来插入数据

//模拟网络请求
- (void)loadData{
    self.mutableArr = [[NSMutableArray alloc]init];
    for (int i = 0 ; i < 10; i++) {
        TestModel *testModel = [[TestModel alloc]init];
        testModel.title = [NSString stringWithFormat:@"这是第%i个标题",i];
        testModel.subTitle = [NSString stringWithFormat:@"这是第%i个幅标题",i];
        [self.mutableArr addObject:testModel];
    }
    [self reloadTableView];
}
//赋值给数据源,刷新tableView
- (void)reloadTableView{
    
    //第一次的时候新创建的NSDiffableDataSourceSnapshot,这样相当于新创建的数据源
    NSDiffableDataSourceSnapshot *snapshot = [[NSDiffableDataSourceSnapshot alloc]init];
    
    //必须先创建Section才可以插入数据
    [snapshot appendSectionsWithIdentifiers:@[@"0"]];
    
    //给snapshot赋值ItemIdentifierTypes
    [snapshot appendItemsWithIdentifiers:self.mutableArr];
    
    //更新数据源的snapshot 刷新tableView
    [self.diffableDataSource applySnapshot:snapshot animatingDifferences:YES];
    
}

我们简单的插入两条数据

- (void)insertNewObject:(id)sender {
    
    NSDiffableDataSourceSnapshot *snapshot = self.diffableDataSource.snapshot;
    
    //不可以每次都创建新的snapshot,否则数据都是新的!
    //NSDiffableDataSourceSnapshot *snapshot = [[NSDiffableDataSourceSnapshot alloc] init];
    
    
    //必须先创建Section才可以插入数据
    [snapshot appendSectionsWithIdentifiers:@[@"1"]];   //SectionIdentifierType不可以重复

    
    TestModel *testModel = [[TestModel alloc]init];
    testModel.title = @"这是新插入的cell";
    //往Section中添加数据,默认添加到最后一个Section中
    [snapshot appendItemsWithIdentifiers:@[testModel]];  //ItemIdentifierType也不可以重复⚠️
    
    //在指定位置之前或之后插入数据
    //[snapshot insertItemsWithIdentifiers:<#(nonnull NSArray *)#> beforeItemWithIdentifier:<#(nonnull id)#>];
    //[snapshot insertItemsWithIdentifiers:<#(nonnull NSArray *)#> afterItemWithIdentifier:<#(nonnull id)#>];
    
    //往指定Section中添加数据
    //[snapshot appendItemsWithIdentifiers:@[testModel]] intoSectionWithIdentifier:@"0"];
    
    [self.diffableDataSource applySnapshot:snapshot animatingDifferences:YES completion:^{
        //
    }];
}

简单的修改数据

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
    TestModel * item = [self.diffableDataSource itemIdentifierForIndexPath:indexPath];
    item.title = @"修改了本行cell的title";
    
    //不能新创建NSDiffableDataSourceSnapshot,直接获取到diffableDataSource中snapshot所有的ItemIdentifierType
    NSDiffableDataSourceSnapshot *snapshot = self.diffableDataSource.snapshot;
    
    //把要刷新的ItemIdentifierTypes放到数组就可以了
    [snapshot reloadItemsWithIdentifiers:@[item]];
    
    //直接把刷新的snapshot放到diffableDataSource,刷新就可以了,会根据ItemIdentifierTypes,刷新数组中添加的ItemIdentifierTypes
    [self.diffableDataSource applySnapshot:snapshot animatingDifferences:YES];
    
}

简单的删除数据

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        
        id item = [self itemIdentifierForIndexPath:indexPath];
        
        //这里注意一下,每次调用self.snapshot都会创建新的对象
        NSDiffableDataSourceSnapshot *snapshot = self.snapshot;
        NSDiffableDataSourceSnapshot *snapshot1 = self.snapshot;   //测试
        NSDiffableDataSourceSnapshot *snapshot2 = self.snapshot;   //测试
        
        //删除的时候不用指定Section,因为每一个item都是唯一的
        [snapshot deleteItemsWithIdentifiers:@[item]];

        [self applySnapshot:snapshot animatingDifferences:YES completion:^{
            //
        }];
        
        //不可以再使用tableview的方法删除
        //[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
        
    } else if (editingStyle == UITableViewCellEditingStyleInsert) {
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
    }
}

当然这个新的API还有许多其他代理方法我就不一一实现了

最后

Diffable Data Source 之前只能依赖三方库的实现的 Features 得到官方原生 UIKIt 的支持,相信用不了多久便可以在生产业务中使用,如电商类购物车,即时聊天等频繁增删的业务场景可以很轻松实现,这是一个拥抱变化的 Apple,值得期待。

相关文章

网友评论

      本文标题:Data Source 新特性:基于 Diffable 实现局部

      本文链接:https://www.haomeiwen.com/subject/igfkgktx.html