自动布局过程

154 阅读26分钟

整体流程如下:

未命名文件 (4).png

讲解这个流程之前,要先搞清楚下面这一点:

如果有多个视图发生了变化,上图中的每个步骤都会把需要处理的视图处理完成后才进入下个步骤,而不是每个视图都走一边上面的流程。

比如上面的例子中,如果修改了两个视图的限制,打印结果是:

2021-04-27 13:13:54.984636+0800 Test[92100:3569483] tjj - 第一个
2021-04-27 13:13:54.984792+0800 Test[92100:3569483] tjj - 第二个
2021-04-27 13:13:54.984951+0800 Test[92100:3569483] tjj - 0x7f9df55109a0, setNeedsLayout
2021-04-27 13:13:54.985084+0800 Test[92100:3569483] tjj - 0x7f9df55109a0, setNeedsLayout
2021-04-27 13:13:54.985182+0800 Test[92100:3569483] tjj - 0x7f9df55109a0, setNeedsLayout
2021-04-27 13:13:54.985409+0800 Test[92100:3569483] tjj - 0x7f9df55109a0, layoutSubviews, 428.000000, 0.000000
2021-04-27 13:13:54.985695+0800 Test[92100:3569483] tjj - 0x7f9df5416dd0, layoutSubviews, 70.000000, 179.000000
2021-04-27 13:13:54.985792+0800 Test[92100:3569483] tjj - 0x7f9df5510f10, layoutSubviews, 70.000000, 0.000000

update constraints

修改限制有两种方式:

1、直接修改限制

2、指定某视图为setNeedsUpdateConstraints,并在该视图的updateConstraints里修改限制

updateConstraints是按照从子视图到父视图的顺序调用的

layout

如果限制的修改,引起了某些视图layout的变化,就会将它们的父视图标记为setNeedsLayout,等待layoutSubviews。(layout变化也就是frame, bounds等的变化)

至于会引起哪些视图layout变化,这是自动布局引擎内部算法完成的,没有详细的解释,总之会把所有layout发生变化的视图的父视图都标记起来。

为什么把父视图标记上,而不是视图本身呢?因为之后layoutSubviews是在父视图上调用的。并且子视图frame发生变化,可能兄弟视图也需要知道这件事,也需要作出相应的修改,在父视图上调用,能做到这一点。

接下来,找到被标记为setNeedsLayout的最顶层的视图,从上到下执行下列操作:一边从自动布局引擎里取出新的frame,给view赋值,一边调用layoutSubviews。

比如,视图层级是:f1是f2的父视图,f2是view的父视图。view的限制发生改变,导致f1, f2的布局都失效,会这样执行:

f1.frame = newframe

[f1 layoutSubviews:]

f2.frame = newframe

[f2 layoutSubviews:]

view.frame = newframe

[view layoutSubviews:]

如果layoutSubviews里还会导致某些视图的布局失效,可能会循环之前的layout步骤

引用

链接:developer.apple.com/videos/play… 字幕原文:

So here is a high-level overview of the process. We start with the application run loop cheerfully iterating until the constraints change in such a way that the calculated layout needs to be different. This causes a deferred layout pass to be scheduled. When that layout pass eventually comes around, we go through the hierarchy and update all the frames for the views. This is a little abstract, so I made a simple example here. The idea is that when we uncheck this top checkbox, we'll modify a constraint to shrink the window and hide the checkboxes on the bottom. So we start with frames looking like this. When we change the constraint, the layout engine's notion of where everything is has already changed, but the UI hasn't updated yet. And then when the layout pass comes along, the UI actually changes to match what the engine thinks should be.

So let's talk about constraint changes.

The constraints that you create are converted to mathematical expressions and kept inside the Layout Engine. So a constraints change is really just anything that affects these expressions, and so that includes some of the obvious things like activating or deactivating constraints or changing the priority or the constant on a constraint, but also less obvious things like manipulating the view hierarchy or reconfiguring certain kinds of controls. Because those may cause constraint changes indirectly.

So what happens when a constraint changes? Well, the first thing that happens is that the Layout Engine will recompute the layout. These expressions are made up of variables that represent things like the origin or the size of a particular view. And when we recalculate the layout, these variables may receive new values. When this happens, the views that they represent are notified, and they mark their superview as needing layout. This is actually what causes the deferred layout pass to be scheduled.

So if we look at the example here, this is where you see the frame actually change in the Layout Engine but not yet in the view hierarchy. So when the deferred layout pass comes along, the purpose of this, of course, is to reposition any views that are not in the right place. So when we are finished, everything is in the right spot.

And a pass is actually a little bit of a misnomer. There are a couple of passes that happen here. The first is for updating constraints. The idea with this is to make sure that if there are any pending changes to constraints, they happen now, before we go to all the trouble to traverse the view hierarchy and reposition all the views. And then the second pass is when we do that view repositioning.

So let's talk about update constraints. Views need to explicitly request that their update constraints method be called. And this pretty much works the same way as setNeedsDisplay. You call setNeedsUpdateConstraints, and then some time later your update constraints method will be called. Really, all this is is a way for views to have a chance to make changes to constraints just in time for the next layout pass, but it's often not actually needed.

All of your initial constraint setup should ideally happen inside Interface Builder. Or if you really find that you need to allocate your constraints programmatically, some place like viewDidLoad is much better. Update constraints is really just for work that needs to be repeated periodically.

Also, it's pretty straightforward to just change constraints when you find the need to do that; whereas, if you take that logic apart from the other code that's related to it and you move it into a separate method that gets executed at a later time, your code becomes a lot harder to follow, so it will be harder for you to maintain, it will be a lot harder for other people to understand. So when would you need to use update constraints? Well, it boils down to performance.

If you find that just changing your constraints in place is too slow, then update constraints might be able to help you out. It turns out that changing a constraint inside update constraints is actually faster than changing a constraint at other times. The reason for that is because the engine is able to treat all the constraint changes that happen in this pass as a batch. This is the same kind of performance benefit that you get by calling activate constraints on an entire array of constraints as opposed to activating each of those constraints individually.

One of the common patterns where we find that this is really useful is if you have a view that will rebuild constraints in response to some kind of a configuration change. It turns out to be very common for clients of these kinds of views to need to configure more than one property, so it's very easy for the view, then, to end up rebuilding its constraints multiple times. That's just a lot of wasted work. It's much more efficient in these kinds of situations to have the view just call setNeedsUpdateConstraints and then when the update constraints pass comes along, it can rebuild its constraints once to match whatever the current configuration is.

In any case, once this pass is complete, we know the constraints are all up-to-date, we are ready to proceed with repositioning the views. So this is where we traverse the view hierarchy from the top down, and we'll call layoutSubviews on any view marked as needing layout. On OS X, this method is called layout, but the idea is the same. The purpose is for the receiver to reposition its subviews. It's not for the receiver to reposition itself.

So what the framework implementation does is it will read frames for the subviews out of the Layout Engine and then assign them. On the Mac we use setFrame for this, and on iOS, it's setBounds and setCenter, but the idea is the same.

So if we look at the example again, this is where you actually see the UI update to match the frames that are in the Layout Engine.

One other note about layoutSubviews: A lot of people will override this in order to get some kind of a custom layout, and it's fine if you need to do this, but there are some things that you need to know because it can be very easy to do things here that can get you into trouble. So I want to look at this in a little more detail.

You should really only need to override layoutSubviews if you need some kind of a layout that just can't be expressed using constraints. If you can find a way to do it using constraints, that's usually more robust, more trouble free.

If you do choose to override this, you should keep in mind that we're in the middle of the layout ceremony at this point. Some views have already been laid out, other views haven't been, but they probably will be soon, and so it's a bit of a delicate moment. There are some special rules to follow. One is that you need to invoke the superclass implementation. We need that for various bookkeeping purposes. Also, it's fine to invalidate the layout of views within your subtree, but you should do that before you call through to the superclass implementation. Second, you don't want to call setNeedsUpdateConstraints. There was an update constraints pass. We went through that, we finished it, and so we missed it. If we still need it now, it's too late. Also, you want to make sure you don't invalidate the layout of views outside your subtree. If you do this, it can be very easy to cause layout feedback loops where the act of performing layout actually causes the layout to be dirtied again. Then we can just end up iterating forever, and that's no fun for anybody.

You'll often find inside a layoutSubviews override that you need to modify constraints in order to get your views in the right places, and that's fine too, but again, you need to be careful. It can be difficult to predict when you modify a constraint what other views in the hierarchy might be affected. So if you are changing constraints, it's very easy to accidentally invalidate layout outside your subtree. In any case, assuming that all this goes smoothly, layout cycle is complete at this point, everything is in the right place, and our constraints change has been fully applied.

So some things to remember about the layout cycle: First, don't expect view frames to change immediately when you modify a constraint. We've just been through this whole process about how that happens later. And if you do find that you need to override layoutSubviews, be very careful to avoid layout feedback loops because they can be a pain to debug. So next I'd like to talk about how Auto Layout interacts with the Legacy Layout system.

翻译成中文并总结:

  1. 自动布局的过程像个黑盒,在外看起来可以简单概括为以下三项的循环:Application Runloop -> Constraints Changes -> Deferred Layout Pass 的过程。其中我认为 Deferred Layout Pass 是重点,强调的是自动布局的核心工作是延迟执行的,不是限制修改后就同步完成的;

  2. Deferred Layout Pass 可以分为两个步骤:更新限制和布局;

  3. 更新限制:需要开发者主动请求更新,也就是setNeedsUpdateConstraints,稍后updateConstraints方法就会被调用了;

  4. updateConstraints方法可能会被误用,比较合适的使用场景是:需要修改多个限制时,比如某次按钮点击后,多个限制需要被修改以响应点击。具体的原因并没有讲清楚,只是说在updateConstraints修改限制的话,自动布局引擎就可以把多个限制批量处理了,效率会提高。

  5. 接下来是布局的阶段,frame变化的视图的父视图的layoutSubviews会被调用

  6. layoutSubviews也可能会被误用:此时的视图树中有些视图已经更新过布局了,有些还没有。如果在layoutSubviews中的修改使得已经更新过布局的视图的布局又失效了,那可能会造成其再次触发layoutSubviews,如果正好触发的是祖先视图,那可能就是个循环了

  7. 重写layoutSubviews要遵守以下规则:

    要调用superclass的layoutSubviews;

    可以使子树视图布局失效,但是要先使其失效,再调用super的layoutSubviews;

    不要调用setNeedsUpdateConstraints,因为已经晚了,更新限制的步骤已经过去了 经过我的实验,是可以用调用setNeedsUpdateConstraints的,并且会导致updateConstraints,并且更新layout,但是不可以再调用自己的setNeedsUpdateConstraints,不然会闪退,对于autolayout来说这是个异常,所以闪退了。

    不要把子树之外的视图的布局失效,否则容易引发循环;

    可以更新限制,但是要小心,因为很容易导致子树之外视图布局失效;

实验代码

#import "LTLifeTestViewController.h"
#import "TFView.h"

@interface LTLifeTestViewController () <UITableViewDelegate, UITableViewDataSource>

// containerView 是 leftView、ltView、rightView 的父视图
// leftView、ltView、rightView 的宽度都是固定值50,top, bottom 都与 containerView 一致,她们三者的限制没有关系
// leftView 靠在父视图的左边,ltView 居中,rightView 靠右
@property (weak, nonatomic) IBOutlet TFView *leftView;
@property (weak, nonatomic) IBOutlet TFView *ltView;
@property (weak, nonatomic) IBOutlet TFView *rightView;
@property (weak, nonatomic) IBOutlet TFView *containerView;
@property (weak, nonatomic) IBOutlet UITableView *tableview;

@property (strong, nonatomic) NSArray *titles;

@end

@implementation LTLifeTestViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.tableview.delegate = self;
    self.tableview.dataSource = self;
    
    self.titles = @[
        @"改动中间的宽度", // 结论:布局等是在runloop即将进入休眠时处理的,并且setNeedsLayout与layoutSubviews是在同一个runloop周期里做完的
        @"update constraint", // setNeedsUpdateConstraints/updateConstraints/setNeedsLayout等是在同一个runloop周期内完成的
        @"修改两个限制", // 对限制作修改后,会在我们的代码结束之后批量操作,setNeedsLayout等
        @"两个update constraint",
        @"修改父视图限制",
    ];
    
    [self addRunLoopObserver]; // 添加RunLoop监听者
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.titles.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }
    cell.textLabel.text = self.titles[indexPath.row];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:NO];
    switch (indexPath.row) {
        case 0:
            [self constraintOnView:self.ltView attribute:NSLayoutAttributeWidth].constant = 70;
            break;
        case 1:
            self.ltView.canUpdateConstraint = YES;
            [self.ltView setNeedsUpdateConstraints];
            break;
        case 2:
            NSLog(@"tjj - 第一个");
            [self constraintOnView:self.ltView attribute:NSLayoutAttributeWidth].constant = 70;
            NSLog(@"tjj - 第二个");
            [self constraintOnView:self.leftView attribute:NSLayoutAttributeWidth].constant = 70;
            break;
        case 3:
            NSLog(@"tjj - 第二个");
            self.rightView.canUpdateConstraint = YES;
            [self.rightView setNeedsUpdateConstraints];
            NSLog(@"tjj - 第一个");
            self.leftView.canUpdateConstraint = YES;
            [self.leftView setNeedsUpdateConstraints];
            break;
        case 4:
            [self constraintOnView:self.containerView attribute:NSLayoutAttributeHeight].constant = 100;
            break;
            
        default:
            break;
    }
}

- (NSLayoutConstraint *)constraintOnView:(UIView *)view attribute:(NSLayoutAttribute)attribute {
    for (NSLayoutConstraint *constraint in view.constraints) {
        if (constraint.firstAttribute == attribute) {
            return constraint;
        }
    }
    return nil;
}

@end

在 TFView 中对 updateConstraintsIfNeeded、updateConstraints、setNeedsUpdateConstraints 、setNeedsLayout、layoutIfNeeded、layoutSubviews、setNeedsDisplay 都做了重写,并打印。

这是点击“两个 update constraints ”的打印结果:


2021-04-27 11:23:06.825532+0800 Test[87411:3502258] tjj - 0x7f85a6512660, setNeedsDisplay
2021-04-27 11:23:06.825767+0800 Test[87411:3502258] tjj - 0x7f85a6615030, setNeedsDisplay
2021-04-27 11:23:06.825963+0800 Test[87411:3502258] tjj - 0x7f85a8a06600, setNeedsDisplay
2021-04-27 11:23:06.826152+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsDisplay
2021-04-27 11:23:06.827577+0800 Test[87411:3502258] kCFRunLoopDefaultMode
2021-04-27 11:23:06.847252+0800 Test[87411:3502258] tjj - 0x7f85a6512660, updateConstraints
2021-04-27 11:23:06.847392+0800 Test[87411:3502258] tjj - 0x7f85a6615030, updateConstraints
2021-04-27 11:23:06.847505+0800 Test[87411:3502258] tjj - 0x7f85a8a06600, updateConstraints
2021-04-27 11:23:06.847621+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, updateConstraints
2021-04-27 11:23:06.847864+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:06.847970+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:06.848074+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:06.848176+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:06.848269+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:06.848364+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:06.848479+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:06.848593+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:06.848693+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:06.848794+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:06.848897+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:06.848992+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:06.849289+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, layoutSubviews, 428.000000, 0.000000
2021-04-27 11:23:06.849431+0800 Test[87411:3502258] tjj - 0x7f85a8a06600, layoutSubviews, 50.000000, 189.000000
2021-04-27 11:23:06.849528+0800 Test[87411:3502258] tjj - 0x7f85a6615030, layoutSubviews, 50.000000, 378.000000
2021-04-27 11:23:06.849638+0800 Test[87411:3502258] tjj - 0x7f85a6512660, layoutSubviews, 50.000000, 0.000000
2021-04-27 11:23:06.859429+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:06.859565+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:06.859677+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:06.859834+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:06.859945+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:06.860092+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:06.922147+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:06.922412+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:06.922499+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:06.922609+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:07.209569+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:07.209747+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:07.209892+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:07.210000+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:07.210200+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:07.211688+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:07.211817+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:07.211956+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:07.212872+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:07.213011+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:07.213104+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:07.213215+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:07.358877+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:07.359057+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:07.359241+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:07.359388+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:07.359604+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:07.361008+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:07.361106+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:07.361181+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:07.361576+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:07.361681+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:07.361795+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:07.361925+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:07.362023+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:07.362153+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:07.862480+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:07.862716+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:07.862836+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:07.862930+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:07.863205+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:07.863325+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:07.863436+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:07.863552+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:07.863681+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:07.863791+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:07.863886+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:07.863972+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:07.864127+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:07.864257+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:07.864366+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:07.864484+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:07.864719+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:07.864791+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:07.867918+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:07.868025+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:10.955924+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:10.956080+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:10.956195+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:10.958489+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:10.958639+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:10.958762+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:10.959097+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:10.959218+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:10.959322+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:10.959514+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:10.959619+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:10.959836+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:10.962009+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:10.962123+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:10.962227+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:10.963261+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:10.963353+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:10.963467+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:10.964471+0800 Test[87411:3502258] tjj - 第二个
2021-04-27 11:23:10.964576+0800 Test[87411:3502258] tjj - 0x7f85a6615030, setNeedsUpdateConstraints
2021-04-27 11:23:10.964715+0800 Test[87411:3502258] tjj - 第一个
2021-04-27 11:23:10.964857+0800 Test[87411:3502258] tjj - 0x7f85a6512660, setNeedsUpdateConstraints
2021-04-27 11:23:10.965191+0800 Test[87411:3502258] tjj - 0x7f85a6512660, updateConstraints
2021-04-27 11:23:10.965353+0800 Test[87411:3502258] tjj - 0x7f85a6615030, updateConstraints
2021-04-27 11:23:10.965496+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:10.965603+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:10.965709+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:10.965811+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, setNeedsLayout
2021-04-27 11:23:10.966182+0800 Test[87411:3502258] tjj - 0x7f85a65120f0, layoutSubviews, 428.000000, 0.000000
2021-04-27 11:23:10.966416+0800 Test[87411:3502258] tjj - 0x7f85a6615030, layoutSubviews, 49.000000, 379.000000
2021-04-27 11:23:10.966523+0800 Test[87411:3502258] tjj - 0x7f85a6512660, layoutSubviews, 49.000000, 0.000000
2021-04-27 11:23:10.967117+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:10.967244+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:10.967379+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:10.967503+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:11.064488+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:11.064777+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:11.064900+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:11.065027+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:11.464508+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:11.464898+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:11.465107+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:11.465239+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:11.465569+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:11.465713+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:11.465877+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:11.465992+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:11.466116+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:11.466219+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:11.466329+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:11.466434+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:11.466570+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:11.466731+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:11.466836+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:11.467015+0800 Test[87411:3502258] 即将休眠
2021-04-27 11:23:11.467283+0800 Test[87411:3502258] 被唤醒
2021-04-27 11:23:11.467376+0800 Test[87411:3502258] 即将处理Timer事件
2021-04-27 11:23:11.467496+0800 Test[87411:3502258] 即将处理Source事件
2021-04-27 11:23:11.470966+0800 Test[87411:3502258] 即将休眠

这个打印可以清楚地看出:

虽然布局过程不是在开发者修改限制后立即发生的,但至少会是在同一个runloop中的