Masonry 的实现其实特别简洁,只有 2800 多行代码,10个类文件,以下是核心类的导图,以及核心流程解析:
NSLayoutConstraint、Masonry 对比
NSLayoutConstraint 添加 AutoLayout 约束
首先,给一个 view 添加 AutoLayout 的原生方法:
- 使用 NSLayoutAttribute 创建一个 NSLayoutConstraint 对象;
- 然后 [view addConstraint:]
1 | UIView *superView = self.view; |
使用 Masonry 添加 AutoLayout 约束
上文添加的约束,使用 Masonry 如下,简化了大概 35 行代码:
1 | [view1 mas_makeConstraints:^(MASConstraintMaker *make) { |
Masonry 添加约束流程解析
mas_makeConstraints 内部逻辑展开如下:
1 | [view1 mas_makeConstraints:^(...) { |
可以从上述例子中看到,Masonry 添加约束调用的 [view1 mas_makeConstraints:] 里,主要做了以下事情:
- 禁用 translatesAutoresizingMaskIntoConstraints 自动约束;
- 初始化一个 ConstraintMaker (约束工厂类),并给 maker 添加 上下左右/宽高 的约束(注意这一步只是将约束记录到 maker 的 constraint array 中);
- 在 maker install 时,将 constraint array 中的约束逐个 add 到 view 上;
1:生成约束(并 record)
1.1 make.left
1 | // 语法分析: |
- 可以看到 make.left 是调用 .left 属性;
- 但由于重写了 left 属性的 get 方法,所以,make.left 会调用 [make addConstraint:] 添加一条 NSLayoutAttributeLeft 类型的约束;
- left 属性的 get 方法,最终 return 了一个 MASConstraint 对象,所以后续才能链式调用;
1 | // MASConstraintMaker.m |
1.2 make.left.and
MASConstraint *mas = make.left;
中可以看到 make.left
返回的是一个 MASConstraint *
类型。
make.left.and.with
and 和 with 作为 get 方法被调用,里边只是简单的 return 了 MASConstraint self,所以可以继续链式调用
1.3 make.left.and.width
MASConstraint *mas = make.left;
中可以看到 make.left
返回的是一个 MASConstraint *
类型。因此,链式调用到 .width 时,调用的不再是 maker 的方法,而是 MASConstraint
的方法:
1 | // MASConstraint.m |
MASConstraint
是一个 Abstract 抽象类:只提供接口,内部是空实现,需要子类处理。因此 width 里调用的 addConstraintWithLayoutAttribute:
会调用到子类 MASViewConstraint
中:
1 | // MASViewConstraint.m |
MASViewConstraint
中并没有生成或添加约束,只是调用 delegate 去处理。这个 delegate
刚好就是前边的 maker
对象,所以又回到了上一步 make.left
一样的逻辑(注意上一步中省略掉没贴的代码):
1 | // MASConstraintMaker.m |
1.4 make.left.and.width.equalTo()
用于 make.left 作为 get 方法返回的是 MASViewConstraint
对象,可以继续链式调用:make.left.equalTo(superView)
,即调用 MASViewConstraint.equalTo();
equalTo
入参是 id 类型的 attribute,attribute
类型可以是 MASViewAttribute、UIView、NSValue
,equalTo 方法内部对类型做了 if 判断;
equalTo 是个宏定义,展开后的调用链是:qualTo(x) -> mas_equalTo(x) -> MASViewConstraint.equalToWithRelation(x, NSLayoutRelationEqual)
注意:
- 这里调用过程中将 NSLayoutRelation 参数也传入到了 MASViewConstraint.equalToWithRelation();
- equalToWithRelation 中保存了 equalTo 传入的
id 类型 attribute
和NSLayoutRelation
到 MASViewConstraint 对象中,并返回 MASViewConstraint 对象,以继续链式调用;
1 | // MASViewConstraint.m |
1.5 make.left.equalTo().offset()
继续链式调用 offset:make.left.equalTo(superView).offset(2);
注:
offset
和equalTo
不同,不是像equalTo
有入参的,所以可以加括号 equalTo(xxx);offset
方法是一个无入参方法,但是方法的返回值是一个有入参有返回值的 block 类型,因此可以 .offset(3) 调用;- 并且,由于 block 的返回值仍是 MASConstraint 对象,所以可以继续链式调用;
简化逻辑如下:
1 | // make.left 是 get 方法 |
1.6 make.xxx
- 其它
make.center
make.insets.and.size
都是一样的用法; make.left.priority(MASLayoutPriorityDefaultLow)
等于make.left.priorityLow
;
2:添加约束
2.1 [make install]
对 MASConstraintMaker *make 添加约束后,make install 最后执行约束:
1 | // MASConstraintMaker.m |
2.2 [constraint install]
1 | // MASViewConstraint.m |
代码技巧
block 灵活使用
block 写法
1 | @property (nonatomic, strong) UIView *(^myBlock)(NSLayoutAttribute attr); |
更多 block 语法,见:How Do I Declare A Block in Objective-C?
block 作为入参简化外部调用
在方法需要传入一个 ConfigModel 时,使用 block 作为入参:让外部无需关注 ConfigModel 初始化方式,只需要关注配置项
1 | // 使用: |
应用案例: SDK 初始化
1 | [SHWAccountSDK setupConfig:^(SHWAccountConfig *config) { |
block 入参作为 result 回调
1 | [SHWAccountSDK getSMSCode:phoneNum success:^(BOOL isSuccess) { |
block 作为返回值,链式调用
通常函数的入参是 block 用的比较多,但函数的返回值是 block 时,可以写出链式调用的优雅写法:
1 | - (MASConstraint * (^)(CGFloat))offset { |
原理:
1 | MASConstraint *mas = [MASConstraint new]; |
对比:
1 | // 返回值不是 block,只是 self class 时 |
MASBoxValue: mas_equalTo、equalTo
mas_equalTo()
可以传入多种类型入参:·
1 | [view1 makeConstraints:^(MASConstraintMaker *make) { |
其定义如下:
1 |
|
这也顺便解释了 mas_equalTo
和 equalTo
的区别:没有区别,mas_equalTo
调用的还是 equalTo
,只是调用前对入参进行了 boxValue
转换类型