Masonry 源代码剖析

Masonry 是一个用来代替苹果原生的AutoLayout 的自动布局框架。这个库的代码量不是很多,而且使用也很简单方便,那么就让我们深入到这个库的内部,看看它是怎么实现的。

使用范例

下面是一个使用Masonry 的范例:

可以看到,view 与superview 之间的约束,是通过一个block 设置的。这个block 接受一个类型为MASConstraintMaker 的参数make,然后在block 函数体内通过make 来实现约束设置。make 看起来像是view 的一个替身,当我们写

时,实际效果是

内部实现

mas_makeConstraints:

在Xcode 中,按住Command,在方法上点击鼠标左键就可以进入方法的内部实现。我们进入mas_makeConstraints:,可以看到这个方法的实现如下:

首先要将被设置的view 的translatesAutoresizingMaskIntoConstraints属性设置成NO,这样才可以成功添加约束。

然后,创建一个MASConstraintMaker的实例。block 接受这个实例,然后执行block 块内的内容。最后返回[constraintMaker install]的结果。

MASConstraintMaker

初始化

在初始化方法中,MASContraintMaker 将要设置的视图赋值给它的属性view,因此 MASContraintMaker 对将要设置的视图拥有了一个弱引用。

block 的执行

block 中实际进行了约束的设置工作。

这个调用过程是链式的,即每次调用都是作用于MASConstraint 类型上,而本次调用的返回类型仍然是MASConstraint 类型。

right,left,bottom,top

right,left,bottom和top 实际上是MASConstraintMaker中的相应属性的getter 方法,以top 为例,其定义如下:

这个方法中还涉及到一些其他方法:

在上面这个方法中:
1. 根据传入的要设置的NSLayoutAttribute 生成对应的MASViewAttribute
2. 根据MASViewAttribute 生成当前视图的新约束。
3. 将2中生成的新约束加入MASContraintMaker 的约束数组中。

equalTo, greaterThanOrEqualTo, lessThanOrEqualTo

这三个方法的参数是id 类型的,因此它们既可以接受NSValue 类型的参数,如NSNumber,CGPoint,CGSize等,也可以接受UIView 类型的参数,还可以接受MASViewConstraint 类型的参数。
当接受NSValue 类型的参数时,如

表示当前视图的top与它的superview 的top相距100;
当接受UIView 类型的参数时,会和UIView 的对应属性做比较,如

表示当前视图和secondView 的top是平齐的;
当接受MASViewConstraint 类型的参数时,如

表示当前视图和secondView 的bottom 是平齐的。

这个特性的实现原理会在稍后进行介绍。

以上方法虽然定义不同,但是内部实现相似,都是调用了MASConstraintequalToWithRelation 方法,但是传入了不同的参数:

继续向下寻找equalToWithRelation 的定义。使用Command + 鼠标左键,我们发现这个方法在三个地方有定义:

MASCompositeConstraintMASViewContraintMASConstraint 的子类。在MASConstraint 中定义了很多抽象方法,都是需要在这两个子类中进行实现的。

MASCompositeConstraint: A composite with a predefined array of children.

从定义和名称中可以看出,MASCompositeConstraint 是约束的组合。按照我的理解,它的作用可以看作是一颗树的根,这个根确定了哪些节点位于这棵树上,而树的子节点的类型都是MASViewConstraint

equalToWithRelation 是一个抽象方法,在MASContraint 中没有实现,如果调用此方法就会抛出一个异常:

MASViewConstraint 重载了此方法:

  1. 判断传入的attribute 是否为NSArray 类型。
  2. 如果不是,则将传入的参数赋值给某些属性。注意secondViewAttribute 这个属性的setter 方法,它并不是一个简单的赋值过程,而是会根据传入的attribute 的类型进行额外的设置。

MASCompositeConstraint 对此方法的实现则要简单得多:

可以看出,这个方法的实现是递归的,它对当前MASCompositeConstraint 的每一个childConstraint 调用对应的equalToWithRelation 方法,如果当前某一个constraint 的childConstraint 为空,就返回这个constraint。怎么样,是不是有点类似于后续遍历?
因为实际的工作都是由MASViewConstraint 类型完成的,下面我们就只关心方法在这个类中的实现。

安装约束

MASConstraintMakerinstall方法中,有如下几个过程:
1. 查看当前视图上已经安装的约束。
2. 对于已经安装的约束,逐一进行卸载。
3. 对于将要安装的约束,逐一[constaint install],进行安装。
4. 将自身的constraints数组中的所有元素清除掉。

MASViewConstraint

我们着重来看上一节描述的[constraintMaker install]中的第三点。

在对代码进行分析之前,我们需要知道:因为每个约束需要处理的,是两个item 之间的关系。

item1.attribute1 = multiplier × item2.attribute2 + constant // 约束等式

所以MASViewConstraint 有两个属性,分别是

这两个属性分别描述了约束等式的左边和右边。
因此,代码的执行过程为:

  1. 将当前约束加入约束等式左边的view 的已安装约束列表中。
  2. 获取当前约束的左右两边的item 和它们对应的layoutAttribute。
  3. 如果item1 描述的不是数值,并且item2 为nil,则当前约束对应着前面所说的equalTo(@100)这种情况,所以当前的约束的item2 应该是item1中view 的superview。这和我们平时手写UI时的逻辑是相同的。
  4. 调用系统API 来进行约束设置。
  5. 如果item2 是一个view,那么这个约束应该被安装在item1 和item2 的共同superview 上,因此需要找到item1 和item2 的最近共同superview。其他情况同理。

小结

本文简要分析了用Masonry 设置视图间布局约束时的代码的内部实现。从分析过程中,我感受到了写一个库的难度,也体会到了那句话的含义:“将简洁留给用户,将复杂留给自己”。无论是SDWebImage 的一行代码设置图片还是Masonry 的链式设置方法,其简洁的背后藏有复杂的逻辑,恼人的边界处理和多种策略的应用。

发表评论

电子邮件地址不会被公开。 必填项已用*标注