I've finally found an answer.
Thanks to warren-burton, to share a link of WWDC presentation, it really helps.
To answer to my question, we should firstly understand what really is the "Update cycle". It consist of three parts. Constraints part(to update all constraints), layout part(to set all frames) and drawing part(to draw a view on the screen). The main reason of misunderstanding in my question was that I don't know that
THE UPDATE CYCLE IS A LOOP
This loop is always running. That means potentially update cycle can run about 120 times per second. It was really bad performance if we would update constraints, layout and redraw 120 times every second. To avoid this problem there are a flags to update layout, update constraints and drawing. So, each update cycle the system checks in each part if it is necessary to call corresponding methods. And methods like updateConstraints()
, layoutSubviews()
and drawRect()
calls only if the corresponding flag is on.
Can we affect these flags?
Yes, we can set the needed flag to true via setNeedsUpdateConstraints()
, setNeedsLayout()
and setNeedsDisplay()
methods. So, if we call setNeedsLayout()
method, the flag is set to true, and the layout will be updated in next update cycle.
Why after setNeedsLayout
layoutsSubviews
method executes immediately
It never executes immediately. It only seems to user that update cycle runs right after calling setNeedsLayout
, because update cycle runs very fast and frequently.
The good example to understand is a changing constraint constant.
print(label.frame.height) // 100
labelHeightConstraint.constant = 200
print(label.frame.height) // 100
After we change the constraint, the system recalculates the frames and call the setNeedsLayout()
to update frames in next update cycle. If you print the height of frame, it would be old, cause the update cycle doesn't occur yet. To see the calculated frame, you should force call the method immediately via layoutIfNeeded()
method
print(label.frame.height) // 100
labelHeightConstraint.constant = 200
layoutIfNeeded()
print(label.frame.height) // 200