传送门:链式编程小Demohtml
这篇文章是 Masonry 框架源码的解析和笔记。学习Masonry以前,先了解这个框架设计的初衷---传统的利用系统API进行纯代码布局的不足。而后,根据Masonry常见的几个链式语法中,顺藤摸瓜地了解Masonry的调用栈。最后,学习并思考这个框架用到的设计模式和链式编程思想。git
+(instancetype)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(nullable id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)c;
复制代码
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = [UIColor yellowColor];
UIView *subView = [[UIView alloc] init];
subView.backgroundColor = [UIColor redColor];
// 在设置约束前,先将子视图添加进来
[self.view addSubview:subView];
// 使用autoLayout约束,禁止将AutoresizingMask转换为约束
[subView setTranslatesAutoresizingMaskIntoConstraints:NO];
// 设置subView相对于VIEW的上左下右各40像素
NSLayoutConstraint *constraintTop = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:40];
NSLayoutConstraint *constraintLeft = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:40];
// 因为iOS坐标系的原点在左上角,因此设置下,右边距使用负值
NSLayoutConstraint *constraintBottom = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-40];
NSLayoutConstraint *constraintRight = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:-40];
// 将四条约束加进数组中
NSArray *array = [NSArray arrayWithObjects:constraintTop, constraintLeft, constraintBottom, constraintRight, nil];
// 把约束条件设置到父视图的Contraints中
[self.view addConstraints:array];
}
复制代码
可见,系统传统的代码布局有点繁琐。为了简化上述传统布局代码,被普遍应用的第三方框架 Masonry 对AutoLayout 进行了封装,Swift版则是 SnapKit。这篇文章就是针对 Masonry 源代码的解析与学习笔记。在这以前,以下图所示,是 Masonry 源代码的结构图:github
mas_makeConstraints
:外部调用#import "Masonry.h"
复制代码
[self.containerView addSubview:self.bannerView];
[self.bannerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.leading.equalTo(self.containerView.mas_leading);
make.top.equalTo(self.containerView.mas_top);
make.trailing.equalTo(self.containerView.mas_trailing);
make.height.equalTo(@(kViewWidth(131.0)));
}];
复制代码
mas_makeConstraints
:实现原理,经过导入的头文件分析#import <Foundation/Foundation.h>
//! Project version number for Masonry.
FOUNDATION_EXPORT double MasonryVersionNumber;
//! Project version string for Masonry.
FOUNDATION_EXPORT const unsigned char MasonryVersionString[];
#import "MASUtilities.h"
#import "View+MASAdditions.h"
#import "View+MASShorthandAdditions.h"
#import "ViewController+MASAdditions.h"
#import "NSArray+MASAdditions.h"
#import "NSArray+MASShorthandAdditions.h"
#import "MASConstraint.h"
#import "MASCompositeConstraint.h"
#import "MASViewAttribute.h"
#import "MASViewConstraint.h"
#import "MASConstraintMaker.h"
#import "MASLayoutConstraint.h"
#import "NSLayoutConstraint+MASDebugAdditions.h"
复制代码
其中
View+MASAdditions
分类为UIView
添加了mas_makeConstraints
方法编程
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
复制代码
@interface MASConstraintMaker () <MASConstraintDelegate>
@property (nonatomic, weak) MAS_VIEW *view;
@property (nonatomic, strong) NSMutableArray *constraints;
@end
复制代码
- (id)initWithView:(MAS_VIEW *)view {
self = [super init];
if (!self) return nil;
self.view = view;
self.constraints = NSMutableArray.new;
return self;
}
复制代码
.top
:经过MASConstraintMaker
类源码分析先分析设置 第一个约束属性 的状况(且惟一一个):例如设计模式
make.top.equalTo(self.containerView.mas_top);
复制代码
- (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]) {
//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;
}
复制代码
该方法返回的newConstraint
是一个MASViewConstraint
类的示例,而MASViewConstraint
类又是MASConstraint
的子类,返回类型写成MASConstraint
没毛病。数组
代码较多,暂时能够只先看if (!constraint)
里面的代码。可见,最后设置 newConstraint
对象代理为self
(即 MASConstraintMaker
),并添加到一开始准备好的 self.constraints 数组中,返回。bash
其中,设置 MASViewConstraint
类 newConstraint
对象的 MASConstraintDelegate
代理为self
(即 MASConstraintMaker
),其做用就是为了可以同时设置多个约束属性!即链式语法。数据结构
@protocol MASConstraintDelegate <NSObject>
/**
* Notifies the delegate when the constraint needs to be replaced with another constraint. For example
* A MASViewConstraint may turn into a MASCompositeConstraint when an array is passed to one of the equality blocks
*/
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint;
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;
@end
复制代码
第2.3.1节的MASConstraintMaker.m
代码中,先是初始化了 MASViewAttribute
对象并保存了 view、item以及 NSLayoutAttribute
三个属性。框架
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
return self;
}
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [super init];
if (!self) return nil;
_view = view;
_item = item;
_layoutAttribute = layoutAttribute;
return self;
}
复制代码
而后又初始化了 MASViewConstraint
对象,内部配置了些默认参数并保存了如上的第一个约束参数 MASViewAttribute
。less
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
self = [super init];
if (!self) return nil;
_firstViewAttribute = firstViewAttribute;
self.layoutPriority = MASLayoutPriorityRequired;
self.layoutMultiplier = 1;
return self;
}
复制代码
.equalTo
:经过基类MASConstraint
及其子类MASViewConstraint
分析第一个约束属性 设置完后,走到.equalTo
时,前面返回已是一个 MASViewConstraint
(继承自MASConstraint
) 对象了,于是调用的是在基类MASConstraint
中声明并实现的block属性getter方法。
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
复制代码
其中,基类 MASConstraint
仅仅声明,并无实现equalToWithRelation
抽象方法。可是,如2.3节中的链式语法.top
,该方法返回的newConstraint
实际是其子类--MASViewConstraint
类的实例,故而可调用子类MASViewConstraint
实现的equalToWithRelation
方法:
- (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;
}
};
}
复制代码
代码较多,暂时可先看else {
里面的代码。
首先是 self.layoutRelation
保存了约束关系且重写了 set
方法,在里面用 self.hasLayoutRelation
这个 BOOL
标识已经有约束关系。
- (void)setLayoutRelation:(NSLayoutRelation)layoutRelation {
_layoutRelation = layoutRelation;
self.hasLayoutRelation = YES;
}
复制代码
而后一样是重写了 self.secondViewAttribute
的 set
方法,这里会根据不一样的状况作不一样的操做。
- (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]) {
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);
}
}
复制代码
其中,第1种状况对应的是:
make.height.equalTo(@20.0f)
复制代码
传入 NSValue
的时, 会直接设置 constraint
的 offset
, centerOffset
, sizeOffset
, 或者 insets
。调用栈以下:
//MASViewConstraint.m
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
[self setLayoutConstantWithValue:secondViewAttribute];
}
//MASConstraint.m
- (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.m
- (void)setOffset:(CGFloat)offset {
self.layoutConstant = offset;
}
//MASViewConstraint.m
- (void)setLayoutConstant:(CGFloat)layoutConstant {
_layoutConstant = layoutConstant;
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
if (self.useAnimator) {
[self.layoutConstraint.animator setConstant:layoutConstant];
} else {
self.layoutConstraint.constant = layoutConstant;
}
#else
self.layoutConstraint.constant = layoutConstant;
#endif
}
复制代码
第2种状况,通常是直接传入一个视图:
make.top.equalTo(self)
复制代码
这时, 就会初始化一个 layoutAttribute
属性与 firstViewArribute
相同的 MASViewAttribute
, 上面的代码就会使视图与 view 顶部对齐。
第3种状况,会传入一个视图的 MASViewAttribute
:
make.top.equalTo(view.mas_bottom);
复制代码
使用这种写法时, 通常是由于约束的方向不一样. 这行代码会使视图的顶部与 view 的底部对齐。
.height.width
:Masonry的链式语法特性make.height.width.equalTo(@20);
复制代码
其中,.height
设置第一个约束属性时,调用的是 MASConstraintMaker.m
中的 .height
, addConstraintWithLayoutAttribute
,以及- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute
。
- (MASConstraint *)height {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}
- (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;
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
复制代码
该方法调用栈返回的是一个MASViewConstraint
(父类是 MASConstraint
) 对象。
所以,经过 .width
设置第二个约束属性的时候,调用的先是基类 MASConstraint.m
中的.width
,而后调用由子类MASViewConstraint
实现的addConstraintWithLayoutAttribute
方法。这时候的调用栈为:
- (MASConstraint *)width {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
MASMethodNotImplemented();
}
复制代码
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
复制代码
这其中,self.delegate
是什么呢?如2.3.1节所述,MASConstraintMaker.m 中设置了 MASViewConstraint
类 newConstraint
对象的 MASConstraintDelegate
代理为“self”
(即 MASConstraintMaker
),其做用就是为了可以同时设置多个约束属性,即链式语法。因此,第二个设置约束属性跟第一个设置约束属性最终 调用的方法同样(都是MASConstraintMaker.m中实现的addConstraintWithLayoutAttribute
)。
- (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;
}
复制代码
当设置 第二次约束属性 并执行完以后,咱们还能够发现 constraint
不为 nil
,而是一个 MASViewConstraint
对象 ,因此该方法调用栈返回的不是 MASViewConstraint
对象,而是 MASCompositeConstraint
这个对象了,下面咱们来看看这个类。
MASCompositeConstraint
MASCompositeConstraint
是约束的集合,它里面有个私有的数组用来存放多个 MASViewAttribute
对象。
make.height.width.equalTo(@20)
复制代码
当设置 第二个约束属性,走到 .width
时,最终走的是:
- (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
判读里面,将 .height
.wight
两条约束 MASViewConstraint
对象塞到数组里,建立 MASCompositeConstraint
对象,而且一样设置了 delegate
,最后还把 self.constraints
里面事先添加好的约束 MASViewConstraint
对象替换成了 MASCompositeConstraint
对象。
#pragma mark - MASConstraintDelegate
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
NSUInteger index = [self.childConstraints indexOfObject:constraint];
NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
[self.childConstraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
复制代码
另外,咱们能够点击 MASCompositeConstraint
初始化方法里看看,它内部会经过 for
循环,把数组里面的全部 MASViewConstraint
对象一样设置了 delegate
。
- (id)initWithChildren:(NSArray *)children {
self = [super init];
if (!self) return nil;
_childConstraints = [children mutableCopy];
for (MASConstraint *constraint in _childConstraints) {
constraint.delegate = self;
}
return self;
}
复制代码
这么作的目的同时是为了可以继续链式调用,好比咱们再设置第三个约束属性 .left
make.height.width.left.equalTo(@20);
复制代码
这时候的调用栈以下:
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
复制代码
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
[self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
return self;
}
- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
id<MASConstraintDelegate> strongDelegate = self.delegate;
MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
newConstraint.delegate = self;
[self.childConstraints addObject:newConstraint];
return newConstraint;
}
复制代码
能够发现,这里又是经过 delegate 方式,调用 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]) {
//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;
}
复制代码
此时,注意到两个 if
体都没有走进去,既不像第一次,也不像第二次约束设置的时候。因此,此次仅仅是初始化了个 MASViewConstraint
对象就直接返回了,而后回到上个方法中添加到 MASCompositeConstraint
的私有数组 self.childConstraints
中返回备用。
关于三次 约束设置以后的 .equalTo(@20)
,由于执行完 .left
时,返回的是 MASCompositeConstraint
对象,到这一步的时候会有点变化,调用栈以下:
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
复制代码
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attr, NSLayoutRelation relation) {
for (MASConstraint *constraint in self.childConstraints.copy) {
constraint.equalToWithRelation(attr, relation);
}
return self;
};
}
复制代码
能够发现,这里会循环以前准备好的私有数组 self.childConstraints
,调用 MASViewConstraint.m 的 equalToWithRelation
方法,和上面讲的同样了。
mas_makeConstraints
方法的最后会调用 [constraintMaker install]
方法来添加全部存储在 self.constraints
数组中的全部约束。
- (NSArray *)install {
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;
}
复制代码
(1). 若是须要从新构建约束,也就是 调用 mas_remakeConstraints:
方法,会先取出视图的全部约束,而后经过一个 for
循环,调用 uninstall
来清空全部约束:
(2). 若是不须要从新构建约束,会取出 self.constraints
数组中准备好的约束,经过 for
循环,调用 install
来把约束添加到视图上。
关于 install
,是基类 MASConstraint
的抽象方法,方法体由MASViewConstraint
或 MASCompositeConstraint
实现。而 MASCompositeConstraint
的 install
方法体中其实也是调用的由MASViewConstraint
类实现的install
。
- (void)install { MASMethodNotImplemented(); }
复制代码
- (void)install {
for (MASConstraint *constraint in self.childConstraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
}
复制代码
这里代码较多,就不分开解析了,直接分为7步写到源码的注释中,以下所示:
- (void)install {
//【1】若是约束以及存在并是 active 会直接返回。
if (self.hasBeenInstalled) {
return;
}
//【2】若是 self.layoutConstraint 响应了 isActive 方法而且不为空,会激活这条约束并添加到 mas_installedConstraints 数组中,最后返回。
if ([self supportsActiveProperty] && self.layoutConstraint) {
self.layoutConstraint.active = YES;
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
return;
}
//【3】这边是获取即将用于初始化 NSLayoutConstraint 的子类 MASLayoutConstraint 的几个属性。
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)
//【4】这边是判断当前即将添加的约束是不是 size 类型的而且 self.secondViewAttribute 也就是约束的第二个参数是 nil,(eg make.left.equalTo(@10))会自动将约束添加到约束的第一个参数视图的 superview 上。
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
//【5】而后就会初始化 NSLayoutConstraint 的子类 MASLayoutConstraint。
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;
//【6】这段代码会先判断是否有约束第二个参数的视图,有的话会寻找约束第一个和第二参数视图的公共 Superview,至关于求两个数的最小公倍数;若是不知足第一个条件,会判断约束第一个参数是不是 size 类型的,是的话直接取到它的视图;最后都不知足会直接取到约束第一个参数视图父视图。
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;
}
//【7】若是须要升级当前的约束就会获取原有的约束,并替换为新的约束,这样就不须要再次为 view 安装约束。若是原来的 view 中不存在能够升级的约束,那么就会在上一步寻找到的 installedView 上面添加约束。
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];
}
}
复制代码
其中第【6】步中的mas_closestCommonSuperview
方法,它会寻找 firstLayoutItem 和 secondLayoutItem 两个视图的公共 superview, 至关于求两个数的最小公倍数.
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
MAS_VIEW *closestCommonSuperview = nil;
MAS_VIEW *secondViewSuperview = view;
while (!closestCommonSuperview && secondViewSuperview) {
MAS_VIEW *firstViewSuperview = self;
while (!closestCommonSuperview && firstViewSuperview) {
if (secondViewSuperview == firstViewSuperview) {
closestCommonSuperview = secondViewSuperview;
}
firstViewSuperview = firstViewSuperview.superview;
}
secondViewSuperview = secondViewSuperview.superview;
}
return closestCommonSuperview;
}
复制代码
make.edges.equalTo(view)
复制代码
咱们再来看看这种写法,调用栈以下:
- (MASConstraint *)edges {
return [self addConstraintWithAttributes:MASAttributeTop | MASAttributeLeft | MASAttributeRight | MASAttributeBottom];
}
- (MASConstraint *)addConstraintWithAttributes:(MASAttribute)attrs {
__unused MASAttribute anyAttribute = (MASAttributeLeft | MASAttributeRight | MASAttributeTop | MASAttributeBottom | MASAttributeLeading
| MASAttributeTrailing | MASAttributeWidth | MASAttributeHeight | MASAttributeCenterX
| MASAttributeCenterY |
......
NSMutableArray *attributes = [NSMutableArray array];
if (attrs & MASAttributeLeft) [attributes addObject:self.view.mas_left];
if (attrs & MASAttributeRight) [attributes addObject:self.view.mas_right];
if (attrs & MASAttributeTop) [attributes addObject:self.view.mas_top];
......
NSMutableArray *children = [NSMutableArray arrayWithCapacity:attributes.count];
for (MASViewAttribute *a in attributes) {
[children addObject:[[MASViewConstraint alloc] initWithFirstViewAttribute:a]];
}
MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithChildren:children];
constraint.delegate = self;
[self.constraints addObject:constraint];
return constraint;
}
复制代码
代码太多省略了一部分,能够发现这段代码做用就是返回一个包含多条约束的 MASCompositeConstraint
对象,接着后面的操做也都是同样的了。
上面3.1中例子的写法还能够改为这样:
make.edges.equalTo(UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f));
复制代码
这里的 equalTo
须要注意下,它是一个宏,定义在 MASConstraint.h 中:
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...) greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...) lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
#ifdef MAS_SHORTHAND_GLOBALS
#define equalTo(...) mas_equalTo(__VA_ARGS__)
#define greaterThanOrEqualTo(...) mas_greaterThanOrEqualTo(__VA_ARGS__)
#define lessThanOrEqualTo(...) mas_lessThanOrEqualTo(__VA_ARGS__)
#define offset(...) mas_offset(__VA_ARGS__)
复制代码
代入上述宏定义,前面的代码等效成:
make.edges.equalTo(MASBoxValue(UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f)));
复制代码
能够发现,其实里面调用的是 MASBoxValue
这个宏,它将 C 和 Objective-C 语言中的一些基本数据结构好比说 double
CGPoint
CGSize
这些值用 NSValue
进行包装。
这里还支持直接调用 size、center 等,具体实现都差很少,就不熬述了:
make.center.equalTo(CGPointMake(0, 50));
make.size.equalTo(CGSizeMake(200, 100));
复制代码
make.height.equalTo(@[redView, blueView])
复制代码
再来看看这种传数组的,在走到 .equalTo
时,最终会调用 MASViewConstraint.m 里面的 equalToWithRelation
方法
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
复制代码
- (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 { .... }
};
}
复制代码
这边仍是遍历数组,而且 MASViewConstraint
实现 NSCopying 协议
,调用 [self copy]
会建立 MASViewConstraint
对象
- (id)copyWithZone:(NSZone __unused *)zone {
MASViewConstraint *constraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:self.firstViewAttribute];
constraint.layoutConstant = self.layoutConstant;
constraint.layoutRelation = self.layoutRelation;
constraint.layoutPriority = self.layoutPriority;
constraint.layoutMultiplier = self.layoutMultiplier;
constraint.delegate = self.delegate;
return constraint;
}
复制代码
而后会根据传的数组里面的 Value 类型来作不一样的操做,前面讲过就不熬述了:
- (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);
}
}
复制代码
最后即是生成 MASCompositeConstraint
对象,并经过 delegate
方式,调用 MASConstraintMaker
的方法,替换 self.constraints
数组里的约束:
- (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];
}
复制代码
MASConstraintMaker
类就是一个工厂类,负责建立MASConstraint
类型的对象(依赖于MASConstraint
接口,而不依赖于具体实现)。在UIView的View+MASAdditions
分类中就是调用的MASConstraintMaker
类中的一些方法。上述咱们在使用Masonry给subView添加约束时,mas_makeConstraints
方法中的Block的参数就是MASConstraintMaker
的对象。用户能够经过该Block回调过来的MASConstraintMaker
对象给View指定要添加的约束以及该约束的值。该工厂中的constraints
属性数组就记录了该工厂建立的全部MASConstraint
对象。
MASConstraintMaker
之因此成为约束工厂类,由于MASConstraintMaker
赋值建立NSLayoutConstraint
对象,由于Masonry将NSLayoutConstraint
类进一步封装成了MASViewConstraint
,因此MASConstraintMaker
是负责建立MASViewConstraint
的对象,并调用MASViewConstraint
对象的Install
方法将该约束添加到相应的视图中。
说了这么多,总结一下,若是你调用maker.top
, maker.left
等等这些方法都会调用下方的工厂方法来建立相应的MASViewConstraint
对象,并记录在工厂对象的约束数组中。之因此能链式调用,就是讲当前的工厂对象(MASConstraintMaker
)指定为MASViewConstraint
对象的代理,因此一个MASViewConstraint
对象就能够经过代理来调用工厂方法来建立另外一个新的MASViewConstraint
对象了,此处用到了代理模式。
角色分析
Client:UIView
,经过分类View+MASAdditions
来扮演
工厂类:MASConstraintMaker
抽象产品:MASConstraint
具体产品:MASViewConstraint
, MASCompositeConstraint
换一种角度看,Masonry 并不是单纯的工厂模式,而是采用了经典的 Composite 设计模式,中文可译做组合模式。
UIView
,经过分类View+MASAdditions
来调用Masonry
MASConstraintMaker
MASConstraint
MASViewConstraint
MASCompositeConstraint
Objective-C是一门动态语言,它使用了一种动态的消息发送机制,即对象(object)或类(class)调用方法。而OC中的点语法则只能经过setter和getter方法做用于类的属性,而不能做用于某个方法。想实现链式语法,只能经过相似block属性的getter方法。
链式编程思想:核心思想为将block做为方法的返回值,且返回值的类型为调用者自己,并将该方法以setter的形式返回,这样就能够实现了连续调用,即为链式编程。
【举例】简单使用链式编程思想实现一个简单计算器的功能:
// CaculateMaker.h
// ChainBlockTestApp
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface CaculateMaker : NSObject
@property (nonatomic, assign) CGFloat result;
- (CaculateMaker *(^)(CGFloat num))add;
@end
复制代码
// CaculateMaker.m
// ChainBlockTestApp
#import "CaculateMaker.h"
@implementation CaculateMaker
- (CaculateMaker *(^)(CGFloat num))add;{
return ^CaculateMaker *(CGFloat num){
_result += num;
return self;
};
}
@end
复制代码
CaculateMaker *maker = [[CaculateMaker alloc] init];
maker.add(20).add(30);
复制代码
经过上面Masonry布局能够看出,它为UIView写了一个category,拓展了mas_makeConstraints
方法,并将MASConstraintMaker
对象做为block的参数传递,在block的实现里完成UIView的布局,提现了函数式编程思想。
// NSObject+Caculate.h
// ChainBlockTestApp
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "CaculateMaker.h"
@interface NSObject (Caculate)
- (CGFloat)caculate:(void (^)(CaculateMaker *make))block;
@end
复制代码
// NSObject+Caculate.m
// ChainBlockTestApp
#import "NSObject+Caculate.h"
@implementation NSObject (Caculate)
- (CGFloat)caculate:(void (^)(CaculateMaker *make))block;{
CaculateMaker *make = [[CaculateMaker alloc] init];
block(make);
return make.result;
}
@end
复制代码
CGFloat result = [NSObject caculate:^(CaculateMaker *maker) {
maker.add(10).add(20).add(30);
}];
NSLog(@"结果为:%.2f",result);
复制代码
Masonry解析
工厂模式
组合模式
链式编程