UIKit rendering - CATransaction
On the previous post, we discovered a mysterious CATransaction.commit
at the bottom of our interrupted layout phase stack. Let’s find out its role in the layout process.
Experimentation
The CATransaction
is little used explicitly. We find few usages of it in our projects at Fabernovel:
Apple’s documentation tells us that the CATransaction
class has two main methods: begin
and commit
. To understand how they work, let’s define a method that can block the main thread for a given time:
Explicit transactions
Usually changes made to a view tree are not effective immediately. They are simply programmed. Indeed, if we block the execution of our code, just after having applied a modification to a tree, it is not immediately visible:
On the other hand, if we surround our modification by the begin and commit methods of CATransaction, the screen refreshes immediately:
Therefore a transaction transfers a view hierarchy to the render server that updates the screen.
Implicit transactions
Apple states it in its documentation:
Any modification made to the view tree must be part of a transaction
In our first example when we changed the color of our sample view, where was the transaction?
Well, CoreAnimation
started one for us. It watches each change that requires a screen refresh (probably with well-placed KVO) and starts a transaction if none is present. We define this transaction as “implicit”.
Implicit transactions are created automatically when the layer tree is modified by a thread without an active transaction
To reveal it, we can use a feature of the transactions: they are nestable. When one transaction is nested into another, the commit of the “child” transaction is effective only at the commit of the parent.
Therefore, if we make two modifications: one, isolated, and the other, encapsulated in an explicit transaction, we find ourselves in the same case as our first example: the screen displays our modification only after three seconds.
The culprit? The implicit transaction. Our transaction is encapsulated by the implicit transaction created by CoreAnimation
when sampleView.backgroundColor = .red
is executed. The modifications will be sent to render server only once the implicit transaction commits them. Our view becomes green only after three seconds.
CATransaction and layout
We can easily verify that a transaction triggers a layout phase at commit time by modifying our buttonAction
method:
This action method lays out the current view hierarchy. It is, in a way, similar to a layoutIfNeeded
:
When you think about it, it’s quite logical that the commit of a transaction is followed by a layout phase. The role of a transaction is to send the current state of the view tree to the render server. It seems that it lays out the view tree before sending it.
So we come to a first element of answer: looking for the next layout phase is looking for the commit of the current implicit transaction.
Indeed, in the stack of our initial code, we do find a call for a transaction commit:
And it is an implicit transaction! The modification of the sample view constraint is the origin of it, we did nothing explicitly.
CATransaction and completionBlock
It is possible to be notified once a transaction completes. This is particularly interesting when a transaction encapsulates animations. The block is only executed once all the programmed animations are completed.
Here is an example from one of our project at Fabernovel:
Here, without any animation, our block is executed on the main queue right after the commit method so once the table view has finished reloading.
In the next post, we will tackle the run loops and what is its link to the transaction commit of our interrupted layout phase stack.