Masonry 的实现其实特别简洁,只有 2800 多行代码,10个类文件,以下是核心类的导图,以及核心流程解析:

NSLayoutConstraint、Masonry 对比

NSLayoutConstraint 添加 AutoLayout 约束

首先,给一个 view 添加 AutoLayout 的原生方法:

  • 使用 NSLayoutAttribute 创建一个 NSLayoutConstraint 对象;
  • 然后 [view addConstraint:]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
UIView *superView = self.view;
UIView *view1 = [UIView new];
[superView addSubView:view1];

// 禁用自动约束
view1.translatesAutoresizingMaskIntoConstraints = NO;

// 创建一个 view1 相对于 superView 的 left 约束
NSLayoutConstraint *leftConstraint
= [NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superView
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:10];
// 省略 30 行
NSLayoutConstraint *rightConstraint;
NSLayoutConstraint *topConstraint;
NSLayoutConstraint *bottomConstraint;

[view1 addConstraint:leftConstraint];
[view1 addConstraint:rightConstraint];
// 省略...

使用 Masonry 添加 AutoLayout 约束

上文添加的约束,使用 Masonry 如下,简化了大概 35 行代码:

1
2
3
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superView).offset(10);
}];

Masonry 添加约束流程解析

mas_makeConstraints 内部逻辑展开如下:

1
2
3
4
5
6
7
8
9
10
[view1 mas_makeConstraints:^(...) {
view1.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *maker = [[MASConstraintMaker alloc] initWithView:view1];

// 调用 block():
maker.left.equalTo(0);
maker.width.and.top.equalTo().offset(2);

[maker install];
}]

可以从上述例子中看到,Masonry 添加约束调用的 [view1 mas_makeConstraints:] 里,主要做了以下事情:

  1. 禁用 translatesAutoresizingMaskIntoConstraints 自动约束;
  2. 初始化一个 ConstraintMaker (约束工厂类),并给 maker 添加 上下左右/宽高 的约束(注意这一步只是将约束记录到 maker 的 constraint array 中);
  3. 在 maker install 时,将 constraint array 中的约束逐个 add 到 view 上;

1:生成约束(并 record)

1.1 make.left

1
2
3
4
// 语法分析:
MASConstraintMaker *make = [MASConstraintMaker new];
// make.left 只是 get 方法( MASConstraintMaker 的 MASConstraint* left 属性)
MASConstraint *mas = make.left;
  • 可以看到 make.left 是调用 .left 属性;
  • 但由于重写了 left 属性的 get 方法,所以,make.left 会调用 [make addConstraint:] 添加一条 NSLayoutAttributeLeft 类型的约束;
  • left 属性的 get 方法,最终 return 了一个 MASConstraint 对象,所以后续才能链式调用;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// MASConstraintMaker.m

- (MASConstraint *)left {
return [self constraint:nil addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {

// 用 view 和 NSLayoutAttribute 构建 MASViewAttribute
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
// 用 MASViewAttribute 构建 MASViewConstraint
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];

// ... 省略

if (!constraint) {
// 作用??
newConstraint.delegate = self;
// 将 MASViewConstraint 添加到 make.constraints(Array) 中,等 install 时再将约束 add 到 view 上
[self.constraints addObject:newConstraint];
}
// return 的 MASViewConstraint 可以继续 链式调用
return newConstraint;
}

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
2
3
4
5
// MASConstraint.m

- (MASConstraint *)width {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}

MASConstraint 是一个 Abstract 抽象类:只提供接口,内部是空实现,需要子类处理。因此 width 里调用的 addConstraintWithLayoutAttribute: 会调用到子类 MASViewConstraint 中:

1
2
3
4
5
// MASViewConstraint.m

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}

MASViewConstraint 中并没有生成或添加约束,只是调用 delegate 去处理。这个 delegate 刚好就是前边的 maker 对象,所以又回到了上一步 make.left 一样的逻辑(注意上一步中省略掉没贴的代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// MASConstraintMaker.m

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {

// 同 make.left ,省略...
// MASViewAttribute *viewAttribute = ;
// MASViewConstraint *newConstraint = ;

// make.left 时,此方法传入的 constraint 是 nil,因此分析 make.left 时,这块代码省略了
// make.left.width 到 .width 时,这里传入的 constraint 就是 make.left 生成的 newConstraint 对象
if ([constraint isKindOfClass:MASViewConstraint.class]) {
// 注: 此处的 constraint 即 left constraint;newConstraint 即 width constraint
NSArray *children = @[constraint, newConstraint];
// MASCompositeConstraint 是 MASConstraint 的子类,constraint group
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;

// 替换约束:
// 替换已经记录到 maker.constraintArray 中的 left constraint 约束为 constraint group
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
// ... 省略
// if 条件成立,已经 return,后续不会再重复 add constraint 到 array
}

// array 替换元素
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
NSUInteger index = [self.constraints indexOfObject:constraint];
[self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}

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 类型 attributeNSLayoutRelation 到 MASViewConstraint 对象中,并返回 MASViewConstraint 对象,以继续链式调用;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// MASViewConstraint.m

- (MASConstraint * (^)(id))mas_equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
// ... 简化

// 保存了 equalTo 传入的 attribute 和 NSLayoutRelation 类型
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
// 继续返回 MASConstraint,链式调用
return self;
};
}

1.5 make.left.equalTo().offset()

继续链式调用 offset:make.left.equalTo(superView).offset(2);

注:

  • offsetequalTo 不同,不是像 equalTo有入参的,所以可以加括号 equalTo(xxx);
  • offset方法是一个无入参方法,但是方法的返回值是一个有入参有返回值的 block 类型,因此可以 .offset(3) 调用;
  • 并且,由于 block 的返回值仍是 MASConstraint 对象,所以可以继续链式调用;

简化逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
// make.left 是 get 方法
MASConstraint* mas = make.left;
MASConstraint*(^block)(CGFloat f) = mas.offset;
// 由于 .offset 的 返回值是 block,所以,可以直接调用:
mas.offset(2);
// mas.offset(2) 等同于:
block(2);


// block 的返回值
MASConstraint *mas2 = mas.offset(2);

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// MASConstraintMaker.m

- (NSArray *)install {

// mas_remakeConstraints 的逻辑:先对已加的约束全部移除,再重新添加
if (self.removeExisting) {
NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
for (MASConstraint *constraint in installedConstraints) {
[constraint uninstall];
}
}

NSArray *constraints = self.constraints.copy;
// 遍历 make 里添加的每个 MASConstraint 并 install
for (MASConstraint *constraint in constraints) {
// mas_updateConstraints 的逻辑: 标记是否需要更新,稍后在 MASViewConstraint install 里更新已加的约束
constraint.updateExisting = self.updateExisting;

// 最终 install 约束的还是在 MASViewConstraint 里
[constraint install];
}
[self.constraints removeAllObjects];
return constraints;
}

2.2 [constraint install]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// MASViewConstraint.m

- (void)install {
// ... 省略:避免重复添加的逻辑

// 取出 MASViewConstraint 记录的 两个 MASViewAttribute 对象
MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;

// ... 省略

// 1、构建一个 NSLayoutAttribute
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];

// ...

// 创建完约束对象后,寻找约束该添加到那个View上:
if (self.secondViewAttribute.view) { // 如果是两个视图相对约束,就获取两种的公共父视图。
MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) { // 如果添加的是Width或者Height,那么就添加到当前视图上
self.installedView = self.firstViewAttribute.view;
} else { // 如果既没有指定相对视图,也不是Size类型的约束,那么就将该约束对象添加到当前视图的父视图上
self.installedView = self.firstViewAttribute.view.superview;
}

// 是否已经添加了约束
MASLayoutConstraint *existingConstraint = nil;

// mas_updateConstraints 的逻辑
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if (existingConstraint) { // 存在,直接重新赋值
existingConstraint.constant = layoutConstraint.constant;
// ...
} else {
// 不存在,添加
// 2、[view addConstraint:NSLayoutAttribute]; !!!
[self.installedView addConstraint:layoutConstraint];
// ...
}
}

代码技巧

block 灵活使用

block 写法

1
2
3
4
5
6
7
8
9
10
11
12
13
@property (nonatomic, strong) UIView *(^myBlock)(NSLayoutAttribute attr);

self.myBlock = ^UIView *(NSLayoutAttribute attr) {
return greenView;
};

// blcok 在右侧时,^ 在最前
dispatch_block_t t = ^void(void) {
};

UIView *(^block)(NSLayoutAttribute attr) = self.myBlock;

self.myBlock = block;

更多 block 语法,见:How Do I Declare A Block in Objective-C?

block 作为入参简化外部调用

在方法需要传入一个 ConfigModel 时,使用 block 作为入参:让外部无需关注 ConfigModel 初始化方式,只需要关注配置项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用:
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(lastView).offset(2);
}];

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {

// new 一个 maker,然后调用 block 传出去
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
// 外部对 maker 配置后,maker install
block(constraintMaker);

return [constraintMaker install];
}

应用案例: SDK 初始化

1
2
3
4
[SHWAccountSDK setupConfig:^(SHWAccountConfig *config) {
config.appKey = @"1";
config.secret = @"2";
}];

block 入参作为 result 回调

1
2
3
4
5
6
7
8
9
10
11
12
[SHWAccountSDK getSMSCode:phoneNum success:^(BOOL isSuccess) {
// getSMSCode 内部 new 了一个 Request 对象;
} failure:^(NSString *errCode, NSString *errMsg) {

}];

MyRequest *request;
[request startWithSuccess:^(BOOL isSuccess) {
NSLog(@"getSMSCodeAsyncWithPhoneNum success");
} failure:^(NSString *errCode, NSString *errMsg) {
NSLog(@"getSMSCodeAsyncWithPhoneNum fail");
}];

block 作为返回值,链式调用

通常函数的入参是 block 用的比较多,但函数的返回值是 block 时,可以写出链式调用的优雅写法:

1
2
3
4
5
6
7
8
9
10
11
- (MASConstraint * (^)(CGFloat))offset {
return ^id(CGFloat offset){
self.offset = offset;
return self;
};
}

// 使用:
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_bottom).offset(padding);
}];

原理:

1
2
3
4
5
6
7
8
MASConstraint *mas = [MASConstraint new];
MASConstraint*(^block)(CGFloat f) = mas.offset;

// 以下相等
block(2);
mas.offset(2);
// 由于 mas.offset 的返回值是一个 (入参是 CGFloat 的)block,在后边直接追加 (2),相当于调用 block
// 并且,由于 block 的返回值仍是 mas 类型,所以后边可以继续链式调用

对比:

1
2
3
4
5
6
7
8
9
// 返回值不是 block,只是 self class 时
- (MASConstraint *)setOffset:(CGFloat)offset {
self.offset = offset;
return self;
}
// 使用:
MASConstraint *mas = [MASConstraint new];
// 只能这样调用
[[mas setOffset:3] setOffset:1];

MASBoxValue: mas_equalTo、equalTo

mas_equalTo() 可以传入多种类型入参:·

1
2
3
4
5
6
7
8
9
10
11
[view1 makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(view2);
make.left.mas_equalTo(3);
make.left.mas_equalTo(@(2));

make.left.mas_equalTo(view2.mas_left);
make.size.mas_equalTo(CGSizeMake(10, 10));

make.edges.equalTo(UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f));
make.height.equalTo(@[redView, blueView])
}];

其定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))

- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return ...;
};
}

// 尽管传入的是 id 类型,但是解析也还是要支持变参和,并将 float,double,int 这样的值类型数据转换成和 equalTo 一样的对象 NSNumber 数据:
static inline id _MASBoxValue(const char *type, ...) {
va_list v;
va_start(v, type);
id obj = nil;
if (strcmp(type, @encode(id)) == 0) {
id actual = va_arg(v, id);
obj = actual;
} else if (strcmp(type, @encode(CGPoint)) == 0) {
CGPoint actual = (CGPoint)va_arg(v, CGPoint);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(CGSize)) == 0) {
CGSize actual = (CGSize)va_arg(v, CGSize);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(MASEdgeInsets)) == 0) {
MASEdgeInsets actual = (MASEdgeInsets)va_arg(v, MASEdgeInsets);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(int)) == 0) {
int actual = (int)va_arg(v, int);
obj = [NSNumber numberWithInt:actual];
} // ... 省略

va_end(v);
return obj;
}

这也顺便解释了 mas_equalToequalTo 的区别:没有区别,mas_equalTo 调用的还是 equalTo,只是调用前对入参进行了 boxValue 转换类型

相关阅读