理解CATransaction

写动画的时候偶尔会用到CATransaction来设置一些动画参数。有时候,调用一个接口做一些动画,但是接口并没有提供一个参数completionBlock,也可以利用CATransaction来添加completionBlock而不用改到别人的接口。

[CATransaction begin];
[CATransaction setCompletionBlock:^{
//complete block
}];
// do animation
[CATransaction commit];

⚠️ CATransaction 踩坑:completion block 没有立即调用

只知道可以这么用,但是CATransaction到底是一个什么东西呢?

CATransaction

官方文档 如是说:

CATransaction is the Core Animation mechanism for batching multiple layer-tree operations into atomic updates to the render tree. Every modification to a layer tree must be part of a transaction. Nested transactions are supported.

CATransaction是事务,用于批量提交多个对layer-tree的操作,并且是原子性的。所有对layer-tree的修改都必须包含在事务内。事务可以嵌套。

嵌套事务

事务允许嵌套。但是嵌套在内层的事务不会立即commit,会在最外层的事务结束后统一commit。

- (void)doUIOperation
{
[CATransaction begin];
// UI operations here
[self doMoreUIOperation];
[CATransaction commit]; // 最外层事务
}
- (void)doMoreUIOperation
{
[CATransaction begin];
// more UI operations here
[CATransaction commit]; // 嵌套事务,等待外层事务commit
}

隐式CATransaction

所有对layer-tree的操作都必须处于事务。修改UIView的属性最终也是修改到了layer-tree。当我们改动到layer-tree时,如果当前没有显式创建过CATransaction,则系统会创建一个隐式的CATransaction,这个隐式CATransaction会在RunLoop结束后commit。

- (void)doUIOperation
{
self.view.backgroundColor = [UIColor redColor]; // 系统自动创建隐式CATransaction
}

显式CATransaction

一般情况下,显式创建CATransaction是为了设置动画参数。但是,CATransaction还有另外一种用处。

- (void)doUIOperation
{
self.view.backgroundColor = [UIColor redColor];
while (YES) {} // 暴力模拟耗时操作
}

如上代码,背景色没有更新为红色。
当我们更新了UI,紧接着做一些耗时的操作,这个时候UI的更新必须要等到耗时操作结束。有几个方案可以解决这个UI更新不及时的问题。

  • 方案一
    最根本的,当然是不应该在主线程做过多耗时的操作,应该尽量将一些耗时操作转移到子线程去完成。优化了主线程的耗时操作之后,自然就不会有UI更新不及时的问题了。

  • 方案二
    有时候有一些耗时操作必须在主线程完成,例如App启动,很多业务都需要进行初始化,这个时候UI更新就会出现不及时的情况。可以通过显示创建CATransaction来解决。

- (void)doUIOperation
{
[CATransaction begin];
self.view.backgroundColor = [UIColor redColor];
[CATransaction commit];
while (YES) {} // 暴力模拟耗时操作
}

但是,凡事都有个但是。如果在创建显式的CATransaction之前有修改View,系统会自动创建隐式的CATransaction。那么再创建显式的CATransaction就是嵌套在隐式CATransaction中的事务了,需要等到RunLoop结束隐式CATransaction提交之后UI才会刷新。

- (void)doUIOperation
{
self.view.backgroundColor = [UIColor blueColor];
[CATransaction begin];
self.view.backgroundColor = [UIColor redColor];
[CATransaction commit];
while (YES) {} // 暴力模拟耗时操作
}

如上代码,背景色没有更新为红色。由此可见,这个方案并不稳定。可能这个时候解决了,后面迭代开发的时候又失效。

  • 方案三
    在更新了UI和耗时操作之前调用[CATransaction flush]
    这个方案非常不建议使用。[CATransaction flush]在RunLoop结束后由系统调用。提前调用有以下缺点:
    • UI更新效率低
    • UI显示异常。如果当前正在做一些动画(如:旋转屏幕)过程中调用了[CATransaction flush]可能会引起UI异常。

以上,方案二不能稳定解决问题,方案三可能会引入副作用。

您的支持将鼓励我继续创作。