Masonry是Objective-C中一款非常火爆的替代原生AutoLayout的第三方框架;相比原生而言,使用起来更简单、优雅。
其实,项目中很多地方都有用到,之前一直没有深入去探究其中的原理,最近得空,来做个简单的分析:
注意:分析之前请先查看MASUtilities.h,这里面重新定义了系统的一些类名(或者叫起别名),以方便理解。
使用方法
//先添加到父视图
[self.view addSubview:_statusLable];
//添加约束
__weak typeof(self) weakSelf = self;
[self.statusLable mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(10);
make.right.mas_equalTo(-10);
make.height.mas_equalTo(21);
make.top.equalTo(weakSelf.tipImage.mas_bottom).with.offset(10);
}];
其中,离我们最接近的就是这个mas_makeConstraints方法了,我们也由此入口来深入展开。
很明显,这个方法的参数是个block,没有返回值,携带一个MASConstraintMaker *类型的参数;然后我们就在这个block内部一通设置,就实现了相关布局,这也太神奇了吧~
其实,作者是把简单的用法留给了用户,复杂的功能自己实现了。
首先,通过分类(View+MASAdditions.h)的方式,给view添加了一系列的方法和属性(mas_left、mas_top等等)
//制作约束
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
//更新约束
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
//重做约束
- (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
方法内部:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
//确定是否将视图的自动调整遮罩转换为“自动布局”约束
self.translatesAutoresizingMaskIntoConstraints = NO;
//生成制作约束类的实例
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
//执行回调,暴露出去,用于设置相关约束
block(constraintMaker);
//安装所有约束并return
return [constraintMaker install];
}
首先,我们需要重点关注
block(constraintMaker);
所有我们之前设置的相关约束,都是通过这个函数传进来的。而这个MASConstraintMaker把约束都存了起来。
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
- (MASConstraint *)bottom {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}
我们发现,使用过程中的.语法挺特别,作者在这里用到了链式调用,我们用以下使用方法来具体分析:
make.left.mas_equalTo(10);
make.left生成一个约束属性,并保存在make中,然后返回一个MASConstraint实例,供我们调用
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
然后接着调用这个实例的equalTo方法
/**
返回block(返回值为MASConstraint类型,参数id类型)
*/
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
上边代码猛地看起来有点儿晕,我们不妨慢慢分析、消化,这里建议倒着推:equalTo方法返回了一个block,那我们在调用方法时,其实就是间接调用了以下block
^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
也就间接调用了equalToWithRelation函数,而我们传入的attribute,此时被equalToWithRelation函数执行,然后返回的MASConstraint实例方便我们继续设置。真是妙极了!block也被玩出了?
我们接着分析equalToWithRelation方法,基本就是通过传入的属性、关系,生成约束,封装、保存,返回。
里边涉及的MASViewConstraint、MASCompositeConstraint其实都是MASConstraint的子类
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
//如果attribute是数组类型
if ([attribute isKindOfClass:NSArray.class]) {
//已经处理过,则报错
NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
NSMutableArray *children = NSMutableArray.new;
//获取所有的属性,生成MASViewConstraint
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;
//设置相关联的view属性
self.secondViewAttribute = attribute;
return self;
}
};
}
我们需要重点关注一下secondViewAttribute方法:
- (void)setSecondViewAttribute:(id)secondViewAttribute {
//根据你传入的值的种类赋值
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
//NSValue类型,则直接赋值,像make.left.mas_equalTo(10);
[self setLayoutConstantWithValue:secondViewAttribute];
} else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
//view,则生成新的MASViewAttribute,赋值给_secondViewAttribute,像make.top.equalTo(self.nameLabel);
_secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
} else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
//像make.left.equalTo(view.mas_right);
//
MASViewAttribute *attr = secondViewAttribute;
if (attr.layoutAttribute == NSLayoutAttributeNotAnAttribute) {
_secondViewAttribute = [[MASViewAttribute alloc] initWithView:attr.view item:attr.item layoutAttribute:self.firstViewAttribute.layoutAttribute];;
} else {
_secondViewAttribute = secondViewAttribute;
}
} else {
NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
}
}
最后就是安装所有约束(及调用maker的install方法)了:
- (NSArray *)install {
//有此标记,表示是重新制作约束
if (self.removeExisting) {
//获取所有已经安装的约束
NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
//逐个移除
for (MASConstraint *constraint in installedConstraints) {
[constraint uninstall];
}
}
//copy一份,用于返回所有已经安装的约束
NSArray *constraints = self.constraints.copy;
//安装新约束
for (MASConstraint *constraint in constraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
//移除所有约束
[self.constraints removeAllObjects];
//返回所有已安装的约束
return constraints;
}
需要深入了解一下MASConstraint子类的install方法:
- (void)install {
//已安装
if (self.hasBeenInstalled) {
return;
}
//可以响应isActive方法并且存在布局约束
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;
//第一个view属性不是size,并且没有第二个属性;
//设置第二个布局对象为第一个view属性的view的父view
//设置第二个布局属性为第一个布局属性
// 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;
//设置key
layoutConstraint.mas_key = self.mas_key;
//第二个view属性里的view有值
if (self.secondViewAttribute.view) {
//获取两个view的共同superview
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);
//设置已安装的view为共同superview
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) {
//如果第一个view属性是尺寸属性
//设置已安装的view为第一个view属性里的view
self.installedView = self.firstViewAttribute.view;
} else {
//设置已安装的view为第一个view属性里的view的superview
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];
}
}
源码至此分析完毕,期间可能忽略了一些内容,大家可以照着代码自行消化。

539

被折叠的 条评论
为什么被折叠?



