[iOS开发]Masonry源码学习
Posted Billy Miracle
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[iOS开发]Masonry源码学习相关的知识,希望对你有一定的参考价值。
苹果提供的自动布局(Auto Layout)能够对视图进行灵活有效的布局。但是,使用原生的自动布局相关的语法创建约束的过程是非常冗长的,可读性也比较差。使用原生的自动布局语法,对于如此简单的一个布局,也是非常冗长的。如果使用 VFL(Visual Format Language)可以有效减少冗余,但是其 ASCII 类型语法使得编译器无法做类型检查,存在一定的安全隐患。
Masonry 的目标其实就是为了解决原生自动布局语法冗长的问题。
Masonry架构
基本组成
Masonry 主要方法由上述例子就可一窥全貌。Masonry主要通过对 UIView
(NSView
)、NSArray
、UIViewController
进行分类扩展,从而提供自动布局的构建方法。
实现流程
先看一个一般的使用Masory
[myView mas_makeConstraints:^(MASConstraintMaker *make)
make.width.height.mas_equalTo(@100);
make.centerX.mas_equalTo(self.view.mas_centerX);
make.top.mas_equalTo(self.view.mas_top).offset(200);
];
接下来看整个添加约束的流程:
先调用
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
先调用UIView
的分类MASAdditions
中的mas_makeConstraints:
方法,在该方法中调用constraintMaker
的初始化方法,传给该方法要设置约束的view
,生产maker
对象,传给block
回调出去,然后再调用maker
的install
方法。
下面看一下初始化方法:
- (id)initWithView:(MAS_VIEW *)view
self = [super init];
if (!self) return nil;
self.view = view;
self.constraints = NSMutableArray.new;
return self;
在initWithView:
会记录下要设置约束的当前view
,同时创建一个数组用来存储即将使用maker
添加的约束。在传给mas_makeConstraints
方法的block
参数中,使用回调出来的maker
进行一一添加约束。下面是使用make.width
点语法后的全部内部调用过程:
// MASConstraintMaker
- (MASConstraint *)width
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
- (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;
// 这里会将原来 make.width 添加的约束 替换成一个 组合约束(宽度约束 + 高度约束)
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
// 返回组合约束
return compositeConstraint;
if (!constraint) // 如果不是在已有约束的基础上再创建约束,则添加约束至列表
newConstraint.delegate = self;// 注意这一步,会对 make.top.left 这种情形产生关键影响
[self.constraints addObject:newConstraint];
return newConstraint;
在第二次设置约束时(.height
)会进入不同的流程。注意上面提到的newConstraint.delegate
设置代理:
//MAConstraint
- (MASConstraint *)height
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
//MSViewConstraint
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
//delegate是MASConstraintMaker
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
// MASConstraintMaker
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute ...
下面看一下.mas_equalTo(@100)
的流程。
// MASConstraint
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
- (MASConstraint * (^)(id))equalTo
return ^id(id attribute)
// attribute 可能是 @100 类似的值,也可能是 view.mas_width等这样的
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
;
- (MASConstraint * (^)(id))mas_equalTo
return ^id(id attribute)
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
;
// MASViewConstraint
- (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;
;
// 设置约束第二项
- (void)setSecondViewAttribute:(id)secondViewAttribute
//判断类型
if ([secondViewAttribute isKindOfClass:NSValue.class])
[self setLayoutConstantWithValue:secondViewAttribute];
else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class])
_secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class])
_secondViewAttribute = secondViewAttribute;
else
NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
// MASConstraint
- (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);
// MASViewConstraint
- (void)setOffset:(CGFloat)offset
self.layoutConstant = offset; // 设置约束常量
- (void)setSizeOffset:(CGSize)sizeOffset
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
switch (layoutAttribute)
case NSLayoutAttributeWidth:
self.layoutConstant = sizeOffset.width;
break;
case NSLayoutAttributeHeight:
self.layoutConstant = sizeOffset.height;
break;
default:
break;
- (void)setCenterOffset:(CGPoint)centerOffset
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
switch (layoutAttribute)
case NSLayoutAttributeCenterX:
self.layoutConstant = centerOffset.x;
break;
case NSLayoutAttributeCenterY:
self.layoutConstant = centerOffset.y;
break;
default:
break;
- (void)setInsets:(MASEdgeInsets)insets
NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
switch (layoutAttribute)
case NSLayoutAttributeLeft:
case NSLayoutAttributeLeading:
self.layoutConstant = insets.left;
break;
case NSLayoutAttributeTop:
self.layoutConstant = insets.top;
break;
case NSLayoutAttributeBottom:
self.layoutConstant = -insets.bottom;
break;
case NSLayoutAttributeRight:
case NSLayoutAttributeTrailing:
self.layoutConstant = -insets.right;
break;
default:
break;
再看如果约束对象是一个控件(mas_equalTo(self.view.mas_top)
),那么就会走进下面的这段代码:
else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class])
_secondViewAttribute = secondViewAttribute;
另外,后面的 offset
方法做了一步额外的操作:
// MASConstraint
- (MASConstraint * (^)(CGFloat))offset
return ^id(CGFloat offset)
self.offset = offset;
return self;
;
- (void)setOffset:(CGFloat)offset
self.layoutConstant = offset;
回到前面,block
执行结束之后,调用了[constraintMaker install]
,下面看一下install
方法的实现:
- (NSArray *)install
// 只有在 mas_remakeConstraints 时,removeExisting 才为 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)
// 设置约束的 updateExisting 属性
// 只有在 mas_updateConstraints 时,updateExisting 才为 YES
constraint.updateExisting = self.updateExisting;
[constraint install];
// 清空 constraints 数组缓存
[self.constraints removeAllObjects];
return constraints;
install
方法内部会对 constraints
列表中的所有约束依次执行各自的 install
方法来添加约束。我们来看一下约束的 install
方法:
// MASCompositeConstraint
- (void)install
for (MASConstraint *constraint in self.childConstraints)
constraint.updateExisting = self.updateExisting;
[constraint install];
// MASViewConstraint
- (void)install
// 约束是否已被添加
if (self.hasBeenInstalled)
return;
// 如果约束支持 isActive 方法,且 self.layoutConstraint 有值了
if ([self supportsActiveProperty] && self.layoutConstraint)
if (@available(ios 8.0, *))
self.layoutConstraint.active = YES;
else
// Fallback on earlier versions
[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
//对齐属性必须具有第二个ViewAttribute。因此我们假设它指的是超视图
// eg make.left.equalTo(@10)
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute)
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
// 生成一个 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;
// 确定约束layoutConstraint 的约束层级(即要被添加到的位置)
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
// 约束存在,则更新constant值
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
else
// 约束不存在,则在该位置添加约束
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
无论是 MASCompositeConstraint
还是 MASViewConstraint
,本质上还是调用 MASViewConstraint
的 install
方法。该方法根据 MASViewConstraint
的各个属性创建一个原生的约束(NSLayoutConstraint
类型),并在定位约束层级后,将约束添加到相应层级的视图上。
重要类
MASLayoutConstraint
@interface MASLayoutConstraint : NSLayoutConstraint
/**
* a key to associate with this constraint
*/
@property (nonatomic, strong) id mas_key;
@end
MASLayoutConstraint
类继承自 NSLayoutConstraint
类。相比其父类,它就多了一个属性 mas_key
。MASLayoutConstraint
用来表示布局约束。