在iOS开发中,Masonry是我们常用的一个轻量级的布局框架,它以链式语法的形式优雅的实现了自动布局。
下面我们以一个典型的布局代码为入口来剖析Masonry
[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.mas_equalTo(CGSizeMake(100.f, 100.f));
make.left.top.offset(@20.f);
}];
通过 mas_makeConstraints 把我们添加的约束写在其提供的block内,非常方便的设置约束关系。可以想象,万变不离其宗,Masonry一定是封装了系统的NSLayoutConstraint。
一、Masonry中的那些类
1、Masonry类关系图
masonry类结构图.png2、核心类的说明
- View+MASAdditions:是方便我们对UIView添加约束所做的一个UIView的分类,里面包括了我们最常用的 mas_makeConstraints、mas_updateConstraints、mas_remakeConstraints 3个方法和一些在添加约束过程中用到的属性。
- MASConstraint:Masony中约束的基类,封装了Masonry约束的通用属性和方法。Masonry不直接使用此类,而是使用它的子类 MSAViewConstraint和MASCompositeConstraint。
- MSAViewConstraint:对NSLayoutConstriant的封装,负责最终产生NSLayoutConstriant对象,并将该对象加载到目标view。
- MASCompositeConstraint:对MSAViewConstraint
的组合,以数组的形式来维护一组约束。当使用诸如 make.left.right 会产生一个约束组合以 MASCompositeConstraint 的形式来管理。 - MASConstraintMaker:一个产生约束的工厂类,也就是我们在block中所使用的make,核心工作是产生约束和装载约束。
- MASViewAttribute:对产生 NSLayoutConstraint 所需元素的封装,包括view、layoutAttribute等关键元素。
- MASLayoutConstraint :继承于 NSLayoutConstraint ,只是增加了一个标识约束的mas_key,用于方便区分约束。
二、核心源码解析
我们从入口方法 mas_makeConstraints 入手,来看看Masonry是如何把约束加载到目标view上的
1、mas_makeConstraints
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
// 关闭自动添加约束,使手动添加的约束生效
self.translatesAutoresizingMaskIntoConstraints = NO;
// 创建约束工厂
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
// 通过block回调,用工厂去创建并记录约束
block(constraintMaker);
// 安装约束并返回约束数组
return [constraintMaker install];
}
该方法首先生成一个maker工厂对象,接着把该对象作为参数传递到我们设置约束的block中,也就是说用这个工厂对象来制造约束,并在最后用install方法来装载所有的约束。
2、make.left 约束的产生
- 在 MASConstraintMaker 中找到left的getter方法
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- 调用方法 addConstraintWithLayoutAttribute,
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
- 接着进入 constraint: addConstraintWithLayoutAttribute: ,注意此时第一个参数constraint为nil,
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
// 创建MASViewAttribute
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
// 根据 viewAttribute 创建 MASViewConstraint
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
// 当链式语法第一次创建约束时候(make.left),contraint为nil
// 第二次(make.left.right),constraint存在,那么组合成MASCompositeConstraint对象
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;
}
这是一个生产约束的核心方法:
到这里我们的make.left就成功产生了一个约束对象,并将该约束对象作为函数的返回值return。
可以看到当 constraint 为 nil 时,产生的约束(MASViewConstraint)被直接添加进 constraints 数组中;
当 constraint 存在时,约束被组合成 MASCompositeConstraint 。
并且以上两种情况约束的代理都被设置成了self(MASConstraintMaker),那我们进入 MASViewConstraint 约束对象中来看看设置这个代理究竟有什么用,
- 进入约束对象 MASViewConstraint, 查看 make.left.right 是如何产生第二个约束right的
从上面可知调用make.left会得到一个 MASViewConstraint 对象,那么make.left.right 产生第二个约束也就是要从第一个约束入手,找到 MASViewConstraint 的基类方法
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
然后进入 addConstraintWithLayoutAttribute
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
从这里我们看到了在产生约束时设置的 delegate ,也就是 MASConstraintMaker 工厂对象,到这里就不用再多赘述了,简单来说就是 maker 产生一个新约束(right)和第一个约束(left)组合成了一个 MASCompositeConstraint 组合约束对象。
- equalTo方法
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
因为内部较繁琐,但不复杂,所以在此就不展开来剖析。总结来讲就是对 MASViewConstraint 或者 MASCompositeConstraint 对象设置 relation 关系(equal)和具体的约束值(@50)。
- nstall方法 装载约束
maker的install方法
// MASConstraintMaker
- (NSArray *)install {
// 是否移除原有约束
if (self.removeExisting) {
// 获取当前view的所有约束
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;
}
由maker的install方法调用具体约束的install方法
// MASViewConstraint
- (void)install {
// 判断约束是否被装载过
if (self.hasBeenInstalled) {
return;
}
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 (NSLayoutConstraint)
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;
// 查找装载约束的View
// 如果 secondView 存在那么递归查找最近的公共父视图
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];
}
// 装载约束到目标View
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];
}
}
最后调用 install 装载约束,到此为止约束就被添加到view上了。
三、链式语法浅析
- MASConstraintMaker 的left方法为例
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
left 方法返回的是一个约束对象,由这个约束对象又可以继续“点”产生第二个约束,这样就实现了连续点。
- MASConstraint 的offset方法
- (MASConstraint * (^)(CGFloat))offset {
// 匿名block,以CGFloat为参数,并返回MASConstraint来支持链式调用
return ^id(CGFloat offset){
self.offset = offset;
return self;
};
}
综上可知,能连续点的条件是“点语法”返回的对象能继续使用“点语法”。
四、结束语
以上,通过一个Masonry布局语句,追根溯源,简要概括了约束的产生和装载的整个过程,并通过Masonry对点语法有了了解。
世界和平万岁!
网友评论