iOS开发-beginUpdates && endUpdates用法

Posted Lotheve

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发-beginUpdates && endUpdates用法相关的知识,希望对你有一定的参考价值。

本篇主要介绍使用beginUpdates和endUpdates方法对UITableView的Cell进行批量操作更新。首先给出过程中依赖的数据源:

    self.arraySections = [NSMutableArray arrayWithCapacity:0];
    NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@"Apple",@"Alice",@"Apache",@"amount",@"application",@"abort",@"action",@"alert", nil];
    NSMutableArray *array2 = [NSMutableArray arrayWithObjects:@"BMW",@"Beach",@"Boom",@"Band",@"BBBB",@"baby",@"bear", nil];
    NSMutableArray *array3 = [NSMutableArray arrayWithObjects:@"Cache",@"Clean",@"clear",@"Class",@"Client",@"count",@"ccc",@"can",@"ccccca",@"c1618", nil];
    [self.arraySections addObject:array1];
    [self.arraySections addObject:array2];
    [self.arraySections addObject:array3];

beginUpdatesendUpdates方法是一对绑定在一起的方法,用来对UITableView批量更新操作。先看一下多个插入删除操作不使用beginUpdatesendUpdates的情况:

    NSMutableArray *array1 = (NSMutableArray *)self.arraySections[0];
    //操作1 删除
    [array1 removeObjectAtIndex:0];
    [self.tableMain deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:0 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
    //操作2 插入
    [array1 insertObject:@"insert" atIndex:3];
    [self.tableMain insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:3 inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
    //操作...

这种做法对于每一个操作,是严格按照顺序执行的,先执行操作1,再执行操作2…再执行任意一个操作的时候,先更新数据源,然后调用表视图的相关操作,该操作方法执行完毕后,会立即调用相关的代理方法更新表视图。更新完毕后继续执行下一个操作…即整个过程是不断地调用代理方法来更新表视图。因此对于这种按序更新的方法,每一个更新操作并不是同时进行的,如果有很多个操作,可能通过肉眼就能看出更新的先后,这通常并不是我们想要的。

通过beginUpdatesendUpdates则可以批量处理操作,区别于上面的操作一个一个执行然后不断更新表视图,批量处理实现了在所有操作执行完后调用代理方法一次性更新表视图,这种方法保证了所有操作最终是同时更新的。

beginUpdatesendUpdates批量更新有三部曲:

  1. 更新数据源(所有操作)
  2. 创建相应的indexPaths数组
  3. 执行操作

下面举例说明:

    NSMutableArray *array1 = (NSMutableArray *)self.arraySections[0];
    [array1 removeObjectAtIndex:0];
    [array1 removeObjectAtIndex:2];
    [array1 insertObject:@"111" atIndex:1];
    [array1 insertObject:@"333" atIndex:3];

    //创建相应的indexPaths数组
    NSArray *indexPathsDelete = @[[NSIndexPath indexPathForRow:0 inSection:0],[NSIndexPath indexPathForRow:2 inSection:0]];
    NSArray *indexPathsInsert = @[[NSIndexPath indexPathForRow:1 inSection:0],[NSIndexPath indexPathForRow:3 inSection:0]];

    //执行操作
    [self.tableMain beginUpdates];
    [self.tableMain deleteRowsAtIndexPaths:indexPathsDelete withRowAnimation:UITableViewRowAnimationFade];

    [self.tableMain insertRowsAtIndexPaths:indexPathsInsert withRowAnimation:UITableViewRowAnimationFade];
    [self.tableMain endUpdates];

beginUpdates和endUpdates方法对之间的操作执行后并没有立刻更新表视图,而是等endUpdates方法返回后才调用代理方法一次性更新的,因此所有更新动画都是同时执行的。

执行前后的表视图显示如下:

执行前:

技术分享

执行后:

技术分享

执行完这段代码,产生如下几个问题:

  • 问题一:代理方法是根据数据源来更新视图的,既然数据源已经更新了,endUpdates返回后代理方法也会执行,那么beginUpdatesendUpdates之间的操作有什么用呢?

    猜想这个过程并不是这么简单的,假如真的只是根据数据源的更新来刷新视图的话,我更新数据源一个reloadData不就搞定了,还要那么麻烦干嘛!怎么验证这个猜想?很简单,把数据源更新后的结果打印出来看看,如果和表视图显示的不一致,那么猜想就是合理的。打印结果如下:

    技术分享

    事实上对比发现,两者确实是不一致的;另外还有一种验证方法,把beginUpdates和endUpdates之间的更新操作全都注释掉,再次运行就发现表视图根本就不更新了,断点发现cellForRowAtIndexPath:根本就没被调用。种种迹象表明,insert/delete这些更新方法定有什么不可告人的秘密!具体这个秘密是什么?是怎么和数据源配合的?我也不得而知!

  • 问题二:beginUpdatesendUpdates之间的insert操作和delete操作交换顺序会影响结果吗?

    答案是不会!交换了两个操作方法,执行结果无差。官方文档中找到了这样的说明:
    The code calls the deleteRowsAtIndexPaths:withRowAnimation: method after it calls insertRowsAtIndexPaths:withRowAnimation:. However, this is not the order in which UITableView completes the operations. It defers any insertions of rows or sections until after it has handled the deletions of rows or sections.
    大致意思就是:deletion操作比insertion操作有更高执行优先权。
    因此在执行insertion操作的时候,deletion操作已经执行完毕。这样就要小心了,因为deletion是在原始表视图的indexPaths上操作的,而insertion则是在deletion操作之后的表视图的indexPaths上操作的,两者参照不同!官方文档中也给了这样的提示:
    Deletion and reloading operations within an animation block specify which rows and sections in the original table should be removed or reloaded; insertions specify which rows and sections should be added to the resulting table.
    端倪在于这个original tableresulting table

  • 问题三:数据源更新顺序对结果有影响吗?

    答案是有!将上面代码中将数据源插入代码放到删除前面,运行结果变了,而且变成一种我看了变天也没看出规律的结果。

    技术分享

    因此再一次证明了问题一种的结论,更新操作函数和数据源真的很有一腿!好了说正经的,如果谁知道操作函数和数据源之间的关联,麻烦告知小弟,非常感谢!!虽然数据源”插入”在”删除”前的结果没有预料到,但是”删除”在”插入”之前的结果还是在意料之中的,因此以后默认就把”删除”写在”插入”前面吧,实属无奈!

下面再看一个例子,用来证明问题二中的相关结论。

//更新数据源
    NSMutableArray *array1 = (NSMutableArray *)self.arraySections[0];
    NSMutableArray *array3 = (NSMutableArray *)self.arraySections[2];
    [array1 removeObjectAtIndex:0];             //删除第0分组第0记录
    [array1 removeObjectAtIndex:2];             //删除第0分组第2记录
    [self.arraySections removeObjectAtIndex:1]; //删除第1分组
    [array1 insertObject:@"1111" atIndex:1];    //插入第0分组第1记录
    [array3 insertObject:@"3333" atIndex:0];    //插入第2分组第0记录

    //创建相应的indexPaths数组
    NSArray *indexpathsDelete = @[[NSIndexPath indexPathForRow:0 inSection:0],[NSIndexPath indexPathForRow:2 inSection:0]];
    NSArray *indexpathsInsert = @[[NSIndexPath indexPathForRow:1 inSection:0],[NSIndexPath indexPathForRow:0 inSection:1]/*要插入的是第2分组第0记录,但这里的分组数却为1,思考原因*/];

    //执行操作
    [self.tableMain beginUpdates];
    [self.tableMain insertRowsAtIndexPaths:indexpathsInsert withRowAnimation:UITableViewRowAnimationFade];
    [self.tableMain deleteSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
    [self.tableMain deleteRowsAtIndexPaths:indexpathsDelete withRowAnimation:UITableViewRowAnimationFade];
    [self.tableMain endUpdates];

执行后界面如下:

技术分享

由于在执行insert之前,delete已经执行完毕了,原始的第1分组已经被删除了,原始第2分组变为第1分组,因此insert方法所要插入的第二个cell的indexPath对应的section为1而不是2。

总结一下:如果只是单纯的一个插入或者删除操作,没必要用beginUpdatesendUpdates包裹操作方法;若是批量操作,建议用endUpdatesendUpdates,已保证界面对各个更新操作同时响应。











以上是关于iOS开发-beginUpdates && endUpdates用法的主要内容,如果未能解决你的问题,请参考以下文章

iOS Table View beginUpdates 视觉错误,黑色单元格

何时在 CarPlay 中使用 beginUpdates() 和 endUpdates()

iOS - UITableView 正确刷新表?

执行“beginUpdates”“endUpdates”时 UITableView 页脚视图的动画问题

UITableView - BeginUpdates/EndUpdates 动画设置

beginUpdates/endUpdates 后出现意外的表格单元格动画