源码解读——Masonry
Masonry 是基于 Apple 的自动布局封装的一个轻量级布局框架。Masonry 通过一种链式的 DSL(Domain-Specific Language)来描述 NSLayoutConstraint
。相比原生的自动布局语法,Masonry 提供了更为简便的语法来构造布局。Masonry 同时支持 iOS 和 Mac OS X。
关于原生的自动布局的详细内容,可以阅读另一篇文章—— 《系统理解 iOS 自动布局》 。
本文所分析的 Masonry 源码版本是 7.4.2
。
Auto Layout VS Masonry
苹果提供的自动布局(Auto Layout)能够对视图进行灵活有效的布局。但是,使用原生的自动布局相关的语法创建约束的过程是非常冗长的,可读性也比较差。
如下所示代码,其作用是让一个子视图填充其父视图,其中子视图的每一边相对父视图缩进 10 像素。
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], ]];
由上可见,使用原生的自动布局语法,对于如此简单的一个布局,也是非常冗长的。如果使用 VFL(Visual Format Language)可以有效减少冗余,但是其 ASCII 类型语法使得编译器无法做类型检查,存在一定的安全隐患。
Masonry 的目标其实就是 为了解决原生自动布局语法冗长的问题 。对于上述示例,使用 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); }];
甚至还可以更加简单:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(superview).with.insets(padding); }];
Masonry 架构
基本组成
Masonry 主要方法由上述例子就可一窥全貌。Masonry 主要通过对 UIView
( NSView
)、 NSArray
、 UIViewController
进行分类扩展,从而提供自动布局的构建方法。相关方法定义在上图所示部分文件中:
View+MASAddtions NSArray+MASAddtions ViewController+MASAddtions
通过分类提供的自动布局构建方法主要有以下这些:
// View+MASAddtions - (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+Addtions - (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; - (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing; - (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
上述自动布局构建方法均使用一个 void(NS_NOESCAPE ^)(MASConstraintMaker *make)
类型的 block 作为参数。的确, MASConstraintMaker
就是 Mansonry 框架中构建布局约束的核心。 MASConstraintMaker
引用了 MASConstraint
的一系列方法及其子类(包括: MASCompositeConstraint
、 MASViewConstraint
),从而实现约束的创建与添加。
MASConstraint
则提供了一系列返回类型为 MASConstraint
的方法,从而实现了链式 DSL,使 Masonry 具备了简洁灵活的优点。
下面,我们依次来介绍 Masonry 框架中的几个重要类:
MASLayoutConstraint MASViewAttribute MASConstraint MAConstraintMaker
MASLayoutConstraint
MASLayoutConstraint
类继承自 NSLayoutConstraint
类。相比其父类,它就多了一个属性 mas_key
。
MASLayoutConstraint
用来表示 布局约束 。
MASViewAttribute
我们知道在自动布局系统中,约束的本质是一个方程式:
item1.attribute1 = multiplier × item2.attribute2 + constant
MASViewAttribute
就是约束方程式中一个 item
与 attribute
组成的单元。
如下所示便是 MASViewAttribute
定义的属性。
@interface MASViewAttribute : NSObject // The view which the reciever relates to. Can be nil if item is not a view. @property (nonatomic, weak, readonly) MAS_VIEW *view; // The item which the reciever relates to. @property (nonatomic, weak, readonly) id item; // The attribute which the reciever relates to @property (nonatomic, assign, readonly) NSLayoutAttribute layoutAttribute; @end
其中,关于 NSLayoutAttribute
枚举所包含的类型,详见 《 系统理解 iOS 自动布局 》中 约束/约束规则/属性 小节。
MASConstraint
MASConstraint
是一个抽象类,主要为其子类 MASViewConstraint
和 MASCompositeConstraint
声明了一些共有的方法。 MASConstraint
为这些共有的方法实现了部分功能,底层的细节实现则由其子类决定。
根据约束方程式的组成,可将这些方法分为以下几类:
- 属性操作方法(Attribute)
- 关系操作方法(Relationship)
- 倍数操作方法(Multiplier)
- 常量操作方法(Constant)
除此之外,还有优先级操作方法。
属性操作方法
属性操作方法根据对应的 NSLayoutAttribute
枚举类型创建约束属性项。
- (MASConstraint *)left; - (MASConstraint *)top; - (MASConstraint *)right; - (MASConstraint *)bottom; - (MASConstraint *)leading; - (MASConstraint *)trailing; - (MASConstraint *)width; - (MASConstraint *)height; - (MASConstraint *)centerX; - (MASConstraint *)centerY; - (MASConstraint *)baseline; - (MASConstraint *)firstBaseline; - (MASConstraint *)lastBaseline; - (MASConstraint *)leftMargin; - (MASConstraint *)rightMargin; - (MASConstraint *)topMargin; - (MASConstraint *)bottomMargin; - (MASConstraint *)leadingMargin; - (MASConstraint *)trailingMargin; - (MASConstraint *)centerXWithinMargins; - (MASConstraint *)centerYWithinMargins;
这些操作方法内部都是通过一个抽象方法实现,须由子类具体实现,该方法为:
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute
关系操作方法
关系操作方法根据 NSLayoutRelation
枚举类型创建约束关系项。
- (MASConstraint * (^)(id attr))equalTo; - (MASConstraint * (^)(id attr))greaterThanOrEqualTo; - (MASConstraint * (^)(id attr))lessThanOrEqualTo;
这些操作方法内部都是通过一个抽象方法实现,须由子类具体实现,该方法为:
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation;
倍数操作方法
两个倍数操作方法都是抽象方法,须由子类具体实现。
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy; - (MASConstraint * (^)(CGFloat divider))dividedBy;
常量操作方法
常量操作方法内部各自调用对应的 setter
方法,而这些 setter
方法都是抽象方法,须由子类具体实现。
- (MASConstraint * (^)(MASEdgeInsets insets))insets; - (MASConstraint * (^)(CGFloat inset))inset; - (MASConstraint * (^)(CGSize offset))sizeOffset; - (MASConstraint * (^)(CGPoint offset))centerOffset; - (MASConstraint * (^)(CGFloat offset))offset; - (MASConstraint * (^)(NSValue *value))valueOffset;
优先级操作方法
后三个优先级操作方法根据 NSLayoutPriority
枚举类型设置约束优先级,其内部都是通过调用第一个优先级操作方法实现的,该方法为抽象方法,须子类具体实现。
- (MASConstraint * (^)(MASLayoutPriority priority))priority; - (MASConstraint * (^)())priorityLow; - (MASConstraint * (^)())priorityMedium; - (MASConstraint * (^)())priorityHigh;
MASViewConstraint
MASViewConstraint
是 MASConstraint
的子类,可以称之为 Masonry 中 最重要的类 。
MASViewConstraint
除了能够 完整表示约束方程式 之外,还存储了约束的 优先级 属性。我们来看一下其外部属性和内部属性。
// Public @property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute; @property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute; // Private @property (nonatomic, strong, readwrite) MASViewAttribute *secondViewAttribute; @property (nonatomic, weak) MAS_VIEW *installedView; // 约束被添加到的位置(视图) @property (nonatomic, weak) MASLayoutConstraint *layoutConstraint; // 约束 @property (nonatomic, assign) NSLayoutRelation layoutRelation; // 关系 @property (nonatomic, assign) MASLayoutPriority layoutPriority; // 优先级 @property (nonatomic, assign) CGFloat layoutMultiplier; // 倍数 @property (nonatomic, assign) CGFloat layoutConstant; // 常量 @property (nonatomic, assign) BOOL hasLayoutRelation; @property (nonatomic, strong) id mas_key; @property (nonatomic, assign) BOOL useAnimator;
我们再来看一下 MASViewConstraint
实现的父类抽象方法。
首先,属性操作方法所调用的一个抽象方法。
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { // 必须是没有设置过布局关系,即 hasLayoutRelation 为 NO NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation"); return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute]; }
可以看到, MASViewConstraint
其实将该方法的具体实现交给了它的代理。
其次,关系操作方法所调用的一个抽象方法。
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { return ^id(id attribute, NSLayoutRelation relation) { if ([attribute isKindOfClass:NSArray.class]) { // 必须是没有设置过布局关系,即 hasLayoutRelation 为 NO NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation"); // 如果 attribute 是一组属性,则生成一组约束 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"); // 如果 attribute 是单个属性,则设置约束的第二项 self.layoutRelation = relation; self.secondViewAttribute = attribute; return self; } }; }
可以看到,针对 attribute
的不同, equalToWithRelation
方法实现了不同的逻辑。
接着,倍数操作方法所调用的两个抽象方法。
- (MASConstraint * (^)(CGFloat))multipliedBy { return ^id(CGFloat multiplier) { NSAssert(!self.hasBeenInstalled, @"Cannot modify constraint multiplier after it has been installed"); self.layoutMultiplier = multiplier; return self; }; } - (MASConstraint * (^)(CGFloat))dividedBy { return ^id(CGFloat divider) { NSAssert(!self.hasBeenInstalled, @"Cannot modify constraint multiplier after it has been installed"); self.layoutMultiplier = 1.0/divider; return self; }; }
可以看到,这两个方法本质上就是修改了 MASViewConstraint
的倍数属性 layoutMultiplier
。
然后,常量操作方法所调用的几个抽象方法。
// 只有约束方程式第一项的属性是: // NSLayoutAttributeLeft、NSLayoutAttributeLeading、 // NSLayoutAttributeTop、NSLayoutAttributeBottom、 // NSLayoutAttributeRight、NSLayoutAttributeTrailing // 时,方法才会有效设置常量属性 - (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; } } // setInsets 的特殊情况 - (void)setInset:(CGFloat)inset { [self setInsets:(MASEdgeInsets){.top = inset, .left = inset, .bottom = inset, .right = inset}]; } // 直接设置常量属性 - (void)setOffset:(CGFloat)offset { self.layoutConstant = offset; } // 只有约束方程式第一项的属性是: // NSLayoutAttributeWidth、NSLayoutAttributeHeight // 时,方法才会有效设置常量属性 - (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; } } // 只有约束方程式第一项的属性是: // NSLayoutAttributeCenterX、NSLayoutAttributeCenterY // 时,方法才会有效设置常量属性 - (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; } }
可以看到,这些 setter
方法会根据 MASViewConstraint
已有的 firstViewAttribute
约束项的约束属性 layoutAttribuet
的类型来设置常量属性。当属性不匹配值,对常量属性的设置并不会生效。
最后,优先级操作方法的一个抽象方法。
- (MASConstraint * (^)(MASLayoutPriority))priority { return ^id(MASLayoutPriority priority) { NSAssert(!self.hasBeenInstalled, @"Cannot modify constraint priority after it has been installed"); self.layoutPriority = priority; return self; }; }
可以看到,该方法内部直接设置了 MASViewConstraint
的优先级属性 layoutPriority
。
MASCompositeConstraint
MASCompositeConstraint
也是 MASConstraint
的子类。与 MASViewConstraint
只表示一个约束不同, MASCompositeConstraint
可以表示一组约束。
@interface MASCompositeConstraint () @property (nonatomic, strong) id mas_key; @property (nonatomic, strong) NSMutableArray *childConstraints; @end
其中, childConstraints
属性持有了一组约束。
我们再来看一下 MASCompositeConstraint
实现的父类抽象方法。
首先,属性操作方法所调用的一个抽象方法。
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { [self constraint:self addConstraintWithLayoutAttribute:layoutAttribute]; return self; }
该方法调用了 MASCompositeConstraint
所实现的 MASConstraintDelegate
的一个方法。
- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { id strongDelegate = self.delegate; MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute]; newConstraint.delegate = self; [self.childConstraints addObject:newConstraint]; return newConstraint; }
可以看出,该方法内部将通过其代理新创建的普通约束或组合约束添加至 MASCompositeConstraint
的 childConstraints
数组中,并设置子约束的代理为 MASCompositeConstraint
的代理。
事实上,在 Masonry 中,下文将要提到的 MASConstraintMaker
充当了所有约束的最终代理,如下图所示。 MASCompositeConstraint
只是充当了转接和补充的作用。
至于关系操作方法、倍数操作方法、常量操作方法、优先级操作方法所调用的抽象方法。 MASCompositeConstraint
对此的实现基本相同,都是对 childConstraints
中的约束进行遍历设置。
MASConstraintMaker
MASConstraintMaker
是 Masonry 的核心。
MASConstraintMaker
指定了构建布局的目标视图以及相关的约束。
@interface MASConstraintMaker () @property (nonatomic, weak) MAS_VIEW *view; @property (nonatomic, strong) NSMutableArray *constraints; @end
MASConstraintMaker
提供了一系列只读的 MASConstraint
属性。这些属性在其 getter
方法内创建了对应 NSLayoutAttribute
枚举类型的约束项。这些属性包括以下:
@property (nonatomic, strong, readonly) MASConstraint *left; @property (nonatomic, strong, readonly) MASConstraint *top; @property (nonatomic, strong, readonly) MASConstraint *right; @property (nonatomic, strong, readonly) MASConstraint *bottom; @property (nonatomic, strong, readonly) MASConstraint *leading; @property (nonatomic, strong, readonly) MASConstraint *trailing; @property (nonatomic, strong, readonly) MASConstraint *width; @property (nonatomic, strong, readonly) MASConstraint *height; @property (nonatomic, strong, readonly) MASConstraint *centerX; @property (nonatomic, strong, readonly) MASConstraint *centerY; @property (nonatomic, strong, readonly) MASConstraint *baseline; @property (nonatomic, strong, readonly) MASConstraint *firstBaseline; @property (nonatomic, strong, readonly) MASConstraint *lastBaseline; @property (nonatomic, strong, readonly) MASConstraint *leftMargin; @property (nonatomic, strong, readonly) MASConstraint *rightMargin; @property (nonatomic, strong, readonly) MASConstraint *topMargin; @property (nonatomic, strong, readonly) MASConstraint *bottomMargin; @property (nonatomic, strong, readonly) MASConstraint *leadingMargin; @property (nonatomic, strong, readonly) MASConstraint *trailingMargin; @property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins; @property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins; @property (nonatomic, strong, readonly) MASConstraint *edges; @property (nonatomic, strong, readonly) MASConstraint *size; @property (nonatomic, strong, readonly) MASConstraint *center; @property (nonatomic, strong, readonly) MASConstraint *(^attributes)(MASAttribute attrs);
上面提到, MASViewConstraint
和 MASCompositeConstraint
都会利用其代理来创建并添加约束项,而它们的代理都是 MASConstraintMaker
。那么,我们来看一下 MASConstraintMaker
对于 MASConstraintDelegate
的实现是怎么样的。
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint { NSUInteger index = [self.constraints indexOfObject:constraint]; NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint); [self.constraints replaceObjectAtIndex:index withObject:replacementConstraint]; } - (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]) { // 如果是在已有约束的基础上再创建的约束,则将它们转换成一个 组合约束,并将原约束替换成该组合约束。 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; }
我们先看 constraint:shouldBeReplacedWithConstraint:
方法,该方法的职责非常简单,就是在已有的约束中查找某个约束并进行替换。
我们再看 constraint:addConstraintWithLayoutAttribute:
方法,该方法是被调用较多的一个方法,其职责主要就是创建并添加约束至 constraints
列表属性中。
工作流程
在了解了 Masonry 的基本组成之后,我们再通过一个示例来介绍一下 Masonry 的工作流程。
示例如下所示。
[view mas_makeConstraints::^(MASConstraintMaker *make) { make.top.equalTo(@10); make.left.equalTo(superview.mas_left).offset(10); make.width.height.equalTo(@100); }];
首先执行分类方法 mas_makeConstraints:
。
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block { self.translatesAutoresizingMaskIntoConstraints = NO; MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; block(constraintMaker); return [constraintMaker install]; }
方法内部先设置 translatesAutoresizingMaskIntoConstraints
为 NO
。因为,Autoresize Mask 和 Auto Layout 是两套布局系统,前者默认可以转换成后者。为了避免前者对自动布局系统产生干扰,这里需要关闭布局转换。
方法内部还会创建一个 MASConstraintMaker
实例,然后以此为参数调用 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) { 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; } // 生成一个 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
类型),并在定位约束层级后,将约束添加到相应层级的视图上。
下面,我们再来看看执行 block 又发生了什么。
首先,看一下 make.top.equalTo(@10);
的执行流程。
// MASConstraintMaker - (MASConstraint *)top { return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop]; } - (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]) { // ... } if (!constraint) { // 如果不是在已有约束的基础上再创建约束,则添加约束至列表 newConstraint.delegate = self; // 注意这一步,会对 make.top.left 这种情形产生关键影响,详见下文 [self.constraints addObject:newConstraint]; } return newConstraint; } // ----------------------------------------------------------------- // 至此,make.top 执行完毕 // ----------------------------------------------------------------- // MASConstraint - (MASConstraint * (^)(id))equalTo { return ^id(id attribute) { // attribute 可能是 @0 类似的值,也可能是 view.mas_width等这样的 return self.equalToWithRelation(attribute, NSLayoutRelationEqual); }; } // MASViewConstraint - (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; } }; } - (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; // 设置约束常量 } // ----------------------------------------------------------------- // 至此,make.top.equalTo(@10) 执行完毕 // -----------------------------------------------------------------
然后,我们再看 make.left.equalTo(superview.mas_left).offset(10);
的执行流程。
其实,这个执行流程也就是执行 equalTo
内部的 setSecondViewAttribute
时有所不同。另外, offset
方法做了一步额外的操作。
// MASViewConstraint - (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); } } // ----------------------------------------------------------------- // 至此,make.left.equalTo(superview.mas_left) 执行完毕 // ----------------------------------------------------------------- // MASConstraint - (MASConstraint * (^)(CGFloat))offset { return ^id(CGFloat offset){ self.offset = offset; return self; }; } - (void)setOffset:(CGFloat)offset { self.layoutConstant = offset; } // ----------------------------------------------------------------- // 至此,make.left.equalTo(superview.mas_left).offset(10) 执行完毕 // -----------------------------------------------------------------
最后,我们再看 make.width.height.equalTo(@100);
的执行流程。
其实到 make.width
这一步与前面没有什么差别,再执行 height
时出现了转换。
// MASConstraint - (MASConstraint *)height { return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight]; } // MASViewConstraint - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation"); // 见上述 make.top.equalTo(@10) 分析代码中的介绍,此时 self.delegate 早已被设置成了 NSConstraintMaker 了 return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute]; } // MASConstraintMaker - (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]) { // 如果是在已有约束的基础上再创建的约束,则将它们转换成一个 组合约束,并将原约束替换成该组合约束。 NSArray *children = @[constraint, newConstraint]; MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children]; compositeConstraint.delegate = self; // 这里会将原来 make.width 添加的约束 替换成一个 组合约束(宽度约束 + 高度约束) [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint]; // 返回组合约束 return compositeConstraint; } if (!constraint) { // ... } // ... } - (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint { NSUInteger index = [self.constraints indexOfObject:constraint]; NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint); [self.constraints replaceObjectAtIndex:index withObject:replacementConstraint]; } // ----------------------------------------------------------------- // 至此,make.width.height 执行完毕 // ----------------------------------------------------------------- // MASConstraint - (MASConstraint * (^)(id))equalTo { return ^id(id attribute) { // attribute 可能是 @0 类似的值,也可能是 view.mas_width等这样的 return self.equalToWithRelation(attribute, NSLayoutRelationEqual); }; } // MASCompositeConstraint - (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { return ^id(id attr, NSLayoutRelation relation) { // CompositeConstraint 的 childConstraits 中每一项,调用 equalToWithRelation for (MASConstraint *constraint in self.childConstraints.copy) { constraint.equalToWithRelation(attr, relation); } return self; }; } // ----------------------------------------------------------------- // 至此,make.width.height.equalTo(@100) 执行完毕 // -----------------------------------------------------------------
总结
Masonry 巧妙利用了面向对象的继承、多态思想以及 block 的特性,从而实现了非常简便的链式 DSL,极大地提升了自动布局开发的效率。