写动画的时候偶尔会用到CATransaction来设置一些动画参数。有时候,调用一个接口做一些动画,但是接口并没有提供一个参数completionBlock,也可以利用CATransaction来添加completionBlock而不用改到别人的接口。
1 | [CATransaction begin]; |
⚠️ 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。
1 | - (void)doUIOperation |
隐式CATransaction
所有对layer-tree的操作都必须处于事务。修改UIView的属性最终也是修改到了layer-tree。当我们改动到layer-tree时,如果当前没有显式创建过CATransaction,则系统会创建一个隐式的CATransaction,这个隐式CATransaction会在RunLoop结束后commit。
1 | - (void)doUIOperation |
显式CATransaction
一般情况下,显式创建CATransaction是为了设置动画参数。但是,CATransaction还有另外一种用处。
1 | - (void)doUIOperation |
如上代码,背景色没有更新为红色。
当我们更新了UI,紧接着做一些耗时的操作,这个时候UI的更新必须要等到耗时操作结束。有几个方案可以解决这个UI更新不及时的问题。
方案一
最根本的,当然是不应该在主线程做过多耗时的操作,应该尽量将一些耗时操作转移到子线程去完成。优化了主线程的耗时操作之后,自然就不会有UI更新不及时的问题了。方案二
有时候有一些耗时操作必须在主线程完成,例如App启动,很多业务都需要进行初始化,这个时候UI更新就会出现不及时的情况。可以通过显示创建CATransaction来解决。
1 | - (void)doUIOperation |
但是,凡事都有个但是。如果在创建显式的CATransaction之前有修改View,系统会自动创建隐式的CATransaction。那么再创建显式的CATransaction就是嵌套在隐式CATransaction中的事务了,需要等到RunLoop结束隐式CATransaction提交之后UI才会刷新。
1 | - (void)doUIOperation |
如上代码,背景色没有更新为红色。由此可见,这个方案并不稳定。可能这个时候解决了,后面迭代开发的时候又失效。
- 方案三
在更新了UI和耗时操作之前调用[CATransaction flush]
。
这个方案非常不建议使用。[CATransaction flush]
在RunLoop结束后由系统调用。提前调用有以下缺点:- UI更新效率低
- UI显示异常。如果当前正在做一些动画(如:旋转屏幕)过程中调用了
[CATransaction flush]
可能会引起UI异常。
以上,方案二不能稳定解决问题,方案三可能会引入副作用。