Masonry源码解析其工作原理

本文深入解析Masonry自动布局库的工作原理,包括系统自动布局约束、Masonry代码实现细节及其链式编程思想。通过实例演示Masonry如何简化iOS界面布局。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

目录

1 系统自带自动布局约束

1.1 有公式

1.2 添加约束到view上的规则

1.3 写一个完整的布局约束

2  步步深入Masonry代码

2.1 代码解析

2.1.1 约束添加过程概览

2.1.2 maker添加约束的过程

2.1.3 install约束(执行[maker install])

3 总结

4 参考文献


Masonry是对系统的自动布局约束的一个封装。要想了解Masonry,首先,我们需要了解下系统的自动布局是怎么样的。

1 系统自带自动布局约束

1.1 有公式

iOS的系统自带的给UIView添加约束的方式:

+(instancetype)constraintWithItem:(id)view1

attribute:(NSLayoutAttribute)attr1

relatedBy:(NSLayoutRelation)relation

toItem:(nullable id)view2

attribute:(NSLayoutAttribute)attr2

multiplier:(CGFloat)multiplier

constant:(CGFloat)c

也即

view1.attr1 = view2.attr2 * multiplier + c (*是相乘的意思)

1.2 添加约束到view上的规则

【规则】:主要看view1 和view2之间到关系

(1)兄弟关系,则约束加到共同的最低父视图

(2)非兄弟关系,则沿着关系树往上找,约束加到共同的最低祖先视图

(3)直接父子关系,则约束加到父视图上

(4)都是自己,则约束加到自己身上

1.3 写一个完整的布局约束

步骤就是:

(1)创建约束(参考1.1) 

(2)将约束添加到View 上(参考1.2)

// 高度约束(添加到yellowView身上)
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:50];
[yellowView addConstraint:heightConstraint];
// 间距约束(添加到self.view身上)
CGFloat margin = 20;
[self.view addConstraints:@[
                            // 左边
                            [NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:margin],
                            
                            // 右边
                            [NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant: - margin],
                            
                            // 底部
                            [NSLayoutConstraint constraintWithItem:yellowView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant: - margin]
                            ]];

 

2  步步深入Masonry代码

2.1 代码解析

贴一张经典的Masonry的类图。

其中,核心类

(1)自定义的约束结构体(MASConstraint、MASViewConstraint、MASCompositeConstraint)

(2)方便用户操作的创建约束的类(MASConstraintMaker)

 MASConstraintMaker 提供方便的调用方法(链式调用)让用户设置约束并生成自定义的约束结构体, MASConstraintMaker install 约束的时候解析这些自定义的约束结构体,生产系统的约束并添加到view上。

2.1.1 约束添加过程概览

首先,我们看看平常使用Masonry添加约束的方式,代码如下:

        [self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.and.bottom.equalTo(self);
            make.top.equalTo(self.topView.mas_bottom);
            make.height.equalTo(self.topView);
        }];

mas_makeConstraints方法位于文件“NSArray+MASAdditions.h”。定位到该方法如下图:

代码所做到步骤如下:

(1)设置约束的translatesAutoresizingMaskIntoConstraints为NO

translatesAutoresizingMaskIntoConstraints 为iOS前期的自动布局属性autoresizing mask,它只能设置父子视图之间的布局,不能设置兄弟视图之间的布局。

YES:自动将这些自动布局属性转换为NSLayoutConstraint对象添加到视图上。比如一些有固有的大小的视图,赋值了image的imageview,会自带有width、height的约束。

NO:不自动将~~~~~~~~~~~~~~~~~~~~~~~~~~~

【问题】:为什么要写死,设置为NO?

translatesAutoresizingMaskIntoConstraints为YES的时候,由于会自动转换一些NSLayoutConstraint对象添加到视图,这些自动添加的NSLayoutConstraint对象有可能会跟我们设置的布局冲突。从而导致布局冲突警告。

(2)创建maker

(3)执行block,也就是

^(MASConstraintMaker *make) {

            make.left.right.and.bottom.equalTo(self);

            make.top.equalTo(self.topView.mas_bottom);

            make.height.equalTo(self.topView);

        }

执行完这些block,会创建出相应的约束结构体,这些结构体会放到maker内部的数组里保存,如下

(4)install maker的constraints数组里面的约束,也就是解析前面创建的约束结构体,生成系统的约束结构体NSLayoutConstraint,然后add到view上,完成约束的最后的添加。

    

2.1.2 maker添加约束的过程

       从上面我们可以看到,maker添加约束的核心代码就在的MASConstraintMaker 的Block里。那么,它是如何通过这个block去创建我们要的约束的呢。下面,我们逐步进入代码进行分析。

我们就举其中的一句代码做栗子

 make.left.right.and.bottom.equalTo(self);

(1) make.left

  • 该约束被加入到maker的constraints数组中。

  • 该约束的delegate = self(即maker)

  • make.left的结果,返回MASViewConstraint类型

  • 此时maker的constraints数组为:

   

maker的constraints数组

left<delegate = maker>

(2)make.left.right

因为(1)make.left返回MASViewConstraint类型,因此,.right的时候调用的是MASViewConstraint类型(left约束)的right函数(即基类MASConstraint的right方法

  • 将已有的left约束和新创建的right约束,放到新创建的MASCompositeConstraint里面,同时把这两个约束的delegate指向CompositeConstraint,把CompositeConstraint的delegate指向maker

  • 并且把原来已经加入到maker的constraints数组的left替换成CompositeConstraint。

  • make.left.right 的结果,返回MASCompositeConstraint 类型

  • 此时,maker 的constraints数组,以及该数组里保存的composite的childConstraints数组内容如下:

 

maker 的constraints数组

对应的CompositeConstraint的childConstraints数组

CompositeConstraint

<delegate = maker>

[left, right]

<delegate = CompositeConstraint>

(3)make.left.right.and

因为(2)make.left.right返回的是MASCompositeConstraint 类型,所以调用MASCompositeConstraint 的and方法(即基类MASConstraint的and方法)

  • 直接返回self,即执行的结果是返回MASCompositeConstraint类型

(4) make.left.right.and.bottom

因为(3)make.left.right.and返回的是MASCompositeConstraint类型,因此调用MASCompositeConstraint的bottom方法。

  • 自从maker创建了composite类型约束,该条语句后续创建的约束都放在composite的childConstraints数组里。

  • childConstraints里的所有约束,其delegate = composite

  • maker的constraints数组里的所有约束,其delegate = maker

  • 返回的最终结果是MASCompositeConstraint类型

maker 的constraints数组

对应的CompositeConstraint的childConstraints数组

CompositeConstraint

<delegate = maker>

[left, right, bottom]

<delegate = CompositeConstraint>

(5)make.left.right.and.bottom.equalTo(self);

因为(4)make.left.right.and.bottom返回的类型为MASCompositeConstraint类型。因此执行MASCompositeConstraint类型的equalTo方法。(即基类的equalTo方法)

  • MASCompositeConstraint 遍历其childConstraints数组里面的约束,对每个约束(MASViewConstraint类型)调用equalToWithRelation函数

可以看到,该函数主要是给约束设置secondViewAttribute 和layoutRelation。分为两种情况。

情况一:类似make.height.equalTo(@[redView, blueView])的情况,即参考view为数组里面的所有view

  • 遍历,复制自己,并分别将layoutRelation和数组里的attribute赋值进去

  • 创建MASCompositeConstraint类型,将其代理设置为自己的代理(compositeConstraint)

  • 将新创建的MASCompositeConstraint 替换掉自己在compositeConstraint的childConstraints数组里的位置

maker 的constraints数组

对应的CompositeConstraint的childConstraints数组

childConstraints数组里的CompositeConstraint的childConstraints数组

CompositeConstraint

<delegate = maker>

[CompositeConstraint_left, CompositeConstraint_right, CompositeConstraint_bottom]

<delegate = CompositeConstraint>

[left1, left2]<delegate = CompositeConstraint_left >

[right1, right]<delegate = CompositeConstraint_right>

,[bottom1,  bottom2]<delegate = CompositeConstraint_bottom>

情况二:只有一个参考view

  • 仅仅将layoutRelation和attribute赋值进去

另外,MASViewConstraint类里面的secondViewAttribute的setter方法里内容很多,里面处理了多种类型的设置。

(6)一个make的约束构建完成。

(7)make.top.equalTo(self.topView.mas_bottom); 和 make.height.equalTo(self.topView)重复上面的过程

后续的每一条make语句与上述流程一样。最后,整个block执行后,maker的数组结构如下:

        [self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.and.bottom.equalTo(self);
            make.top.equalTo(self.topView.mas_bottom);
            make.height.equalTo(self.topView);
        }];

maker的constraints数组

CompositeConstraint的childConstraints数组

执行的代码

CompositeConstraint

<delegate = maker>

[left, right, bottom]

<delegate = CompositeConstraint>

make.left.right.and.bottom.equalTo(self);

top(MASViewConstraint)

<delegate = maker>

 

make.top.equalTo(self.topView.mas_bottom)

height(MASViewConstraint)

<delegate = maker>

 

make.height.equalTo(self.topView)

 

2.1.3 install约束(执行[maker install])

1. MASCompositeConstraint的install方法

MASCompositeConstraint的install方法,会遍历其childConstraints里面的约束结构体(一般都是MASViewConstraint),最终执行MASViewConstraint的install方法。

2. MASViewConstraint的install方法

 

3 总结

       Masonry的思路:提供一套方便用户使用的接口(方便用户设置参照View、约束数据),将约束相关的数据封装起来,然后再解析封装好的约束对象,生成系统的NSLayoutConstraint对象,按照系统的规则添加到UIView上。

(1)通过属性和 block 的方式实现链式调用

  基本思想就是通过block参数,并且返回对象本身,从而进一步点语法调用下一个属性

   Masonry 的MASConstraint这个类中的大部分方法的都返回一个block,而block的返回值都是MASConstraint,或者方法直接返回MASConstraint类型(self), 返回的MASConstraint对象又可以调用返回block的方法,正是通过这样的方式使链式语法能够工作。

(2)组合模式

首先,经典 组合模式 中的参与者:

【Client】:通过 Component 接口操纵组合部件的对象。

【Component】:为组合中的对象声明接口。在适当的情况下,实现所有类共有接口的缺省行为。声明一个接口用于访问和管理 Component 的子组件。。在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。

【Leaf】:在组合中表示叶节点对象,叶节点没有子节点。在组合中定义图元对象的行为。

【Composite】:定义有子部件的那些部件的行为。在 Composite 接口中实现与子部件有关的操作。

 

 那么,我们从 组合模式 的角度看,Masonry 框架中的角色分析:

  UIView,通过分类View+MASAdditions来调用Masonry

【Client】:MASConstraintMaker

【Component】:MASConstraint

【Leaf】:MASViewConstraint

【Composite】:MASCompositeConstraint

 

4 参考文献

1 Masonry源码分析

iOS 源代码分析 — Masonry

iOS开发之Masonry框架源码解析

4 iOS框架·Masonry源码深度解析及学习启示:设计模式与链式编程思想

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值