Masonry源码阅读1

Masonry

Masonry是一个轻量级的布局框架,它使用更好的语法包装AutoLayout。 Masonry有自己的布局DSL,它提供了一种描述NSLayoutConstraints的可链接方式,从而使布局代码更简洁,更易读。 Masonry支持iOS和Mac OS X。

AutoLayout

先看一下,如果使用原生的 AutoLayout,需要那些代码

子视图离父视图上下左右各有10个像素的间距

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
UIView *superview = self.view;

UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[superview addConstraints:@[

//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],

[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],

[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],

[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],

]];

但如果使用 Masonry 的话,代码量以及阅读起来都十分可观

1
2
3
4
5
6
7
8
9
10
11
12
13
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

更简单的方法
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];

是不是非常简洁,调用起来方便,阅读起来也清晰明了

Masonry 会自动将约束添加到适当的视图中, 且自动调用 view1.translatesAutoresizingMaskIntoConstraints = NO;

Masonry

看一下 mas_makeConstraints 方法声明

1
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;

这个方法传递的参数是一个参数为 MASConstraintMaker 类型的无返回值的 block,而该方法的返回值则是一个数组。

方法声明中我们看到了一个叫做 NS_NOESCAPE 的宏,NS_NOESCAPE 用于修饰方法中的 block 类型参数,作用是告诉编译器,该 block 在方法返回之前就会执行完毕,而不是被保存起来在之后的某个时候再执行。编译器被告知后,就会相应的进行一些优化。更详细的内容请参考 Add @noescape to public library API

看一下 mas_makeConstraints 方法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
// 默认情况下,view 上的 autoresizing mask 会产生约束条件,以完全确定视图的位置。
// 这允许 AutoLayout 系统跟踪其布局被手动控制的 view 的 frame(例如通过 setFrame:)。
// 当你选择通过添加自己的约束来使用 AutoLayout 来定位视图时,必须 self.translatesAutoresizingMaskIntoConstraints = NO;
// IB 会自动为你做这件事。
self.translatesAutoresizingMaskIntoConstraints = NO;
// 通过view初始化一个MASConstraintMaker
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
// 将 constraintMaker 传递给block,方便我们在外部调用 make.top.equalTo(superview.mas_top).with.offset(padding.top);
block(constraintMaker);
// 返回添加的约束数组
return [constraintMaker install];
}

看一下 MASConstraintMaker 的 init 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@interface MASConstraintMaker () <MASConstraintDelegate>

@property (nonatomic, weak) MAS_VIEW *view; // 弱指针保留 view
@property (nonatomic, strong) NSMutableArray *constraints; // 约束数组

@end

- (id)initWithView:(MAS_VIEW *)view {
self = [super init];
if (!self) return nil;

self.view = view;
self.constraints = NSMutableArray.new;

return self;
}

看一下 block(constraintMaker); 将 constraintMaker 传递给我们,方便使用属性添加约束

1
2
3
4
make.top.equalTo(superview.mas_top).with.offset(padding.top);
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);

看下 make.top

make 是 MASConstraintMaker 类型的对象,这个类型封装了一系列只读 MASConstraint 属性,top 就是其中之一,声明和实现如下:

1
2
3
4
@property (nonatomic, strong, readonly) MASConstraint *top;
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}

addConstraintWithLayoutAttribute 方法的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

// 间接调用了 `constraint:addConstraintWithLayoutAttribute:` 方法:

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
if ([constraint isKindOfClass:MASViewConstraint.class]) {
//replace with composite constraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
return newConstraint;
}

再来看下 equalTo 方法,也是用 MASConstraint 调用的,并且返回 MASConstraint 对象,方便链式调用

1
2
3
4
5
6
7
8
9
10
11
12
13
- (MASConstraint * (^)(id attr))equalTo;
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { MASMethodNotImplemented(); }
#define MASMethodNotImplemented() \
//
@throw [NSException exceptionWithName:NSInternalInconsistencyException \
reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
userInfo:nil]

而我们在 makeConstraints 的时候,实际调用的是 MASViewConstraint 这个 MASConstraint 子类中的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
if ([attribute isKindOfClass:NSArray.class]) {
NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
NSMutableArray *children = NSMutableArray.new;
for (id attr in attribute) {
MASViewConstraint *viewConstraint = [self copy];
viewConstraint.layoutRelation = relation;
viewConstraint.secondViewAttribute = attr;
[children addObject:viewConstraint];
}
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self.delegate;
[self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
} else {
NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
return self;
}
};
}

该方法接收两个参数,一个表示了对应的属性(mas_top),一个表示了相等关系(NSLayoutRelationEqual),进入方法后会先对我们传入的属性做一个类型判断,我们传入的是一个单个的属性,所以会落入 else 分支,同样是依赖断言做了一系列保护性的判断,并将相等关系和视图属性分别赋值给 layoutRelation 和 secondViewAttribute 属性,并返回 self。

返回 self,看似简简单单的一个操作,却是 Masonry 能够实现链式 DSL 最重要的基石。

superview.mas_top

再来看看传入的 mas_top,这是一个声明在 View+MASAdditions.h 当中的只读属性:

1
2
3
4
5
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;

- (MASViewAttribute *)mas_top {
return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTop];
}

.with

1
2
3
- (MASConstraint *)with {
return self;
}

.offset

1
2
3
4
5
6
7
8
- (MASConstraint * (^)(CGFloat offset))offset;

- (MASConstraint * (^)(CGFloat))offset {
return ^id(CGFloat offset){
self.offset = offset;
return self;
};
}

[constraintMaker install];

在配置好想要的约束后,调用 [constraintMaker install]; 对视图施加约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (NSArray *)install {
if (self.removeExisting) {
NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
for (MASConstraint *constraint in installedConstraints) {
[constraint uninstall];
}
}
NSArray *constraints = self.constraints.copy;
for (MASConstraint *constraint in constraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
[self.constraints removeAllObjects];
return constraints;
}

对 constraints 属性做一份 copy后,遍历 constraints 中的所有 MASConstraint 及其子类型的属性,并调用其 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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
- (void)install {
// 是否已经 install 了
if (self.hasBeenInstalled) {
return;
}

// 如果 supportsActiveProperty 且 layoutConstraint 不为空,则将 layoutConstraint.active 设为 YES,并将其添加到 firstViewAttribute.view 的 mas_installedConstraints 只读属性中去
if ([self supportsActiveProperty] && self.layoutConstraint) {
self.layoutConstraint.active = YES;
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
return;
}

// 生成约束
MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;

// alignment attributes must have a secondViewAttribute
// therefore we assume that is refering to superview
// eg make.left.equalTo(@10)
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}

MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];

layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;

// 施加约束
if (self.secondViewAttribute.view) {
MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
NSAssert(closestCommonSuperview,
@"couldn't find a common superview for %@ and %@",
self.firstViewAttribute.view, self.secondViewAttribute.view);
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) {
self.installedView = self.firstViewAttribute.view;
} else {
self.installedView = self.firstViewAttribute.view.superview;
}


MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
} else {
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}


-------------The End-------------

本文标题:Masonry源码阅读1

文章作者:kysonyangs

发布时间:2019年01月05日 - 11:01

最后更新:2020年05月17日 - 22:05

原始链接:https://kysonyangs.github.io/default/Masonry源码阅读1/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。