Masonry源码剖析
版别:v1.1.0
github链接
引子
首要咱们依据较为完好的运用,与NSLayoutConstraints
作对比,然后再逐步剖析Masonry完成主动布局的过程。
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.backgroundColor = UIColor.redColor;
[superview addSubview:view1];
//Masonry布局
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);
}];
//NSLayoutConstraint布局
[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布局是简洁很多的,可读性也较高。
当然,Masonry是经过链式调用
的方式简化了代码,实践上最后也是转化为NSLayoutConstraint布局
(下文会具体剖析)。
剖析过程
咱们从上面的mas_makeConstraints
布局视点动身,剖析Masonry怎么做到如此简洁地完结一个view的束缚。主要剖析过程为
一、给谁做束缚
二、怎么组成束缚
三、怎么完结束缚
一、给谁做束缚
经过检查mas_makeConstraints
办法的定义可知,束缚目标类型为MAS_VIEW
,在MASUtilities.h
文件中能够找到其宏定义。
#if TARGET_OS_IPHONE || TARGET_OS_TV
#define MAS_VIEW UIView
#elif TARGET_OS_MAC
#define MAS_VIEW NSView
#endif
这儿咱们仅针对iOS架构来剖析,能够得出:是给UIView做束缚。
二、怎么组成束缚
咱们先来剖析这一句代码,研讨各个链式调用
是怎么终究组成一句束缚的,咱们能够先从make
下手
make.left.equalTo(superview.mas_left).with.offset(padding.left);
(1)make是何物?
由mas_makeConstraints
调用得知,make为MASConstraintMaker类型目标,能够理解为帮忙创立束缚的类。那其对应的top
、left
、right
等等特点是什么概念呢?
(2)MASConstraintMaker的top
、left
等特点
① 检查定义得知,这些特点定位在MASConstraintMaker
类中,回来了MASConstraint
类型
@property (nonatomic, strong, readonly) MASConstraint *left;
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
②以left
为例,实践的操作为,构造一个MASViewConstraint
(继承自MASConstraint
)目标并回来,其firstViewAttribute
的item
为当前做束缚的view,layoutAttribute为对应的NSLayoutAttributeLeft
。
- (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;
}
如上咱们得到MASConstraint
类型目标,能够算是承认了需求创立束缚的具体特点,然后将该束缚增加到constraints
数组特点中。
接下来便是equalTo(superview.mas_left).with.offset(padding.left);
操作,咱们先来研讨MASConstraint
的equalTo(...) / mas_equalTo(...)
操作。
(3)MASViewConstraint的equalTo(...) / mas_equalTo(...)
办法
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
#define equalTo(...) mas_equalTo(__VA_ARGS__)
- (MASConstraint * (^)(id))mas_equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
//MASViewConstraint的equalToWithRelation办法
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
if ([attribute isKindOfClass:NSArray.class]) {
//……
} else {
NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
return self;
}
};
}
该办法回来了一个block,传入attribute
回来MASConstraint
类型目标。
此办法承认了MASViewConstraint的layoutRelation为NSLayoutRelationEqual
, 而传入的参数(superview.mas_left)即为secondViewAttribute
。
(4)MASViewConstraint的with/and
办法
- (MASConstraint *)with {
return self;
}
- (MASConstraint *)and {
return self;
}
这两个办法回来了self
,其实的起到了连接语义的作用,一般能够省掉。接下来咱们看看offset办法
(5)MASViewConstraint的offset(...)/mas_offset(...)
办法
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
#define offset(...) mas_offset(__VA_ARGS__)
- (MASConstraint * (^)(NSValue *value))valueOffset {
return ^id(NSValue *offset) {
NSAssert([offset isKindOfClass:NSValue.class], @"expected an NSValue offset, got: %@", offset);
[self setLayoutConstantWithValue:offset];
return self;
};
}
- (void)setLayoutConstantWithValue:(NSValue *)value {
if ([value isKindOfClass:NSNumber.class]) {
self.offset = [(NSNumber *)value doubleValue];
} else if (strcmp(value.objCType, @encode(CGPoint)) == 0) {
CGPoint point;
[value getValue:&point];
self.centerOffset = point;
} else if (strcmp(value.objCType, @encode(CGSize)) == 0) {
CGSize size;
[value getValue:&size];
self.sizeOffset = size;
} else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) {
MASEdgeInsets insets;
[value getValue:&insets];
self.insets = insets;
} else {
NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value);
}
}
- (void)setOffset:(CGFloat)offset {
self.layoutConstant = offset;
}
能够看出offset办法会依据传入的类型,去设置offset/centerOffset/sizeOffset/insets
特点。而设置这些特点,最后都会承认对应束缚特点的layoutConstant
。
(6)MASViewConstraint的multipliedBy(...)
办法
- (MASConstraint * (^)(CGFloat))multipliedBy {
return ^id(CGFloat multiplier) {
NSAssert(!self.hasBeenInstalled,
@"Cannot modify constraint multiplier after it has been installed");
self.layoutMultiplier = multiplier;
return self;
};
}
由于MASViewConstraint目标初始化时layoutMultiplier为1.0时,所以layoutMultiplier为1.0时常常不写。这儿也便是将layoutMultiplier
特点赋值。
(7)总结
make.left.equalTo(superview.mas_left).with.offset(padding.left);
至此,这一整句代码的流程就能够总结为:
设置MASViewConstraint目标的以下特点
firstViewAttribute.item = view1
firstViewAttribute.layoutAttribute = NSLayoutAttributeLeft
layoutRelation = NSLayoutRelationEqual
secondViewAttribute.item = superview
secondViewAttribute.layoutAttribute = NSLayoutAttributeLeft
layoutMultiplier = 1.0
layoutConstant = padding.left
对比以下代码,有没有感觉和NSLayoutConstraint
写束缚时的参数如出一辙
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
有了这些特点之后,Masonry自然就能够经过mas_makeConstraints/mas_updateConstraints/mas_remakeConstraints
办法进行增加/更新/移除后增加
相应束缚。
接下来咱们经过剖析mas_makeConstraints
办法,了解Masonry怎么完结束缚。
三、怎么完结束缚
咱们先来看mas_makeConstraints
的完成
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
//translatesAutoresizingMaskIntoConstraints 默认情况下,视图上的主动调整掩码会产生彻底确定的束缚视图的方位。这答应主动布局体系盯梢视图的帧。布局是手动操控的(例如,经过-setFrame:)。当您选择经过增加自己的束缚来运用主动布局来定位视图时,您必须将此特点设置为NO
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
能够看到该办法首要创立了constraintMaker
,然后调用block,也便是设置并增加了block中的每一条束缚到constraints
中,然后回来[constraintMaker install]
,咱们就来研讨MASConstraintMaker的install办法。
(1)MASConstraintMaker的install
办法
- (NSArray *)install {
//removeExisting在mas_remakeConstraints情况下为YES
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;
}
①首要,假如调用的是mas_remakeConstraints
会先卸载之前的束缚
②将constraints中的每条束缚执行install,然后清空constraints。此时咱们应该检查MASConstraint的install办法
(2)MASConstraint的install
办法
- (void)install {
//...
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];
}
}
代码较多,这儿就简略总结一下。
① 假如secondViewAttribute =nil,且束缚特点不为width和height时,默认设置secondLayoutItem为view.superview、secondLayoutAttribute为firstViewAttribute.layoutAttribute。eg make.left.equalTo(@10)
②用对应的特点构造了MASLayoutConstraint
束缚目标
③确定要设置安装束缚的installedView为 (1)最近的公共先人view (2)宽高特点束缚时,为firstViewAttribute.view (3)firstViewAttribute.view.superview
④判断是否是更新束缚,是则更新,不是则installedView
增加该束缚 ([self.installedView addConstraint:layoutConstraint];) 。
由于MASLayoutConstraint继承自NSLayoutConstraint,所以这儿installedView (MAS_VIEW类型)的addConstraint
办法,其实也便是UIView的addConstraint
办法,也就成功地为installedView增加上了该束缚。
//Adds a constraint on the layout of the receiving view or its subviews.
- (void)addConstraint:(NSLayoutConstraint *)constraint API_AVAILABLE(ios(6.0)); // This method will be deprecated in a future release and should be avoided. Instead, set NSLayoutConstraint's active property to YES.
总结
(1)Masonry增加束缚流程总结
经过block中的每一行创立一个束缚MASViewConstraint
,增加到束缚数组constraints
中,经过install
办法逐条增加到对应的installedView
上。
(2)equalTo和mas_equalTo、offset和mas_offset功能根本相同,只不过mas_equalTo
和mas_offset
增加了对数字字面量的支持(即_MASBoxValue)。
make.height.equalTo(123.0); //error: Passing 'double' to parameter of incompatible type '__strong id'
make.height.mas_equalTo(123.0);
(3)假如束缚特点不为宽高,且equalTo/mas_equalTo
的目标是父view的相同特点,可省掉mas_equalTo
。(即不写equalTo/mas_equalTo,默认相对父view布局)
[self.view addSubview:view1];
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
//由于view1.superview = self.view, 所以以下两句代码效果相同
make.right.mas_equalTo(self.view.mas_right).mas_equalTo(-20);
make.right.mas_equalTo(-20);
}];