Masnory做了什么?
简单说,只是简化了代码实现Autolayout的方式,本质还是系统API实现的(后面会讲到)。
创建一个view
,左边距离self.view
左边50
。简单对比一下:
- 系统API:
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1
constant:50];
[self.view addConstraint:constraint];
- Masnory:
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(50);
}];
简单看来,系统API更繁琐,Masnory
更简洁,当创建很多约束时这个特点就尤为突出,那么Masnory
是怎么做到既简洁又好用的呢?
Masnory怎么做到的?
首先得说说里面几个关键类:
- MASViewConstraint
/ (id) item (or (UIView *) view)
(MASViewAttribute *) firstViewAttribute
/ \(NSLayoutAttribute) layoutAttribute
(MASViewAttribute *) secondViewAttribute ...
/
MASViewConstraint
\
(NSLayoutRelation) layoutRelation
\
(CGFloat) layoutMultiplier
\
(CGFloat) layoutConstant
...
这个类它是Masnory
的核心,为什么这样说?看看它的其中一个方法-install
就知道,其中有一段实现特别关键:
MASLayoutConstraint *layoutConstraint = [MASLayoutConstraint constraintWithItem:self.firstViewAttribute.item
attribute:self.firstViewAttribute.layoutAttribute
relatedBy:self.layoutRelation
toItem:self.secondViewAttribute.item
attribute:self.secondViewAttribute.layoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
是不是和NSLayoutConstraint
的创建很相似,因为MASLayoutConstraint
本就是继承自NSLayoutConstraint
,此处的self
是指MASViewConstraint
。你猜对了,Masnory
在用MASViewConstraint
的属性值创建一个NSLayoutConstraint
对象,里面的所有类都会围绕MASViewConstraint
做文章(后面会一一分析),所以也印证了开头总结的,Masnory
本质还是使用了系统的API在做约束。
- MASConstraintMaker
这是服务MASViewConstraint
很重要的一个类,更准确说是服务MASConstraint
,因为MASViewConstraint
继承自MASConstraint
。它是MASConstraint
的工厂类,里面有一个工厂方法会频繁用到,先提在这儿,后面也会重点介绍。
- (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;
}
- MASCompositeConstraint
这也是继承自MASConstraint
,它有个很重要的功能,单次执行超过1个约束以上,就会用到它,它有一个数组childConstraints
专门保存每个约束对应的MASViewConstraint
对象。(PS:什么叫单次执行,这是我的理解,例如:make.left.top.equalTo(self.view).offset(60); 这个单次执行就会有两个约束left、top,这时就会产生一个MASCompositeConstraint
对象。)
几个重点类和概念心里先有个谱,现在才正式跟踪代码,看具体怎么实现。
Part1 只围绕一个简单的写法来跟代码,先走通一个方式,后续再添加其他情况。
例子很简单,在self.view
上添加imageView
距离上下左右都是60
UIImageView *imageView = [[UIImageView alloc]init];
[self.view addSubview:imageView];
@WeakObj(self);
[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
@StrongObj(self);
make.left.top.equalTo(self.view).offset(60);
make.right.bottom.equalTo(self.view).offset(-60);
}];
1、链式语法:
如果你对它很熟悉,可以略过。
链式语法我是这样理解的:
我把里面的涉及到方法,left、top、equalTo、offset
都看作同一个类型的属性,然后用get方法串起来的,说起来有点抽象,不过还原一下就知道了。这里拿equalTo
举例:
@property(copy, nonatomic) MASConstraint *(^equalTo)(id);
- (MASConstraint *(^)(id))equalTo{
}
2、分析工厂方法
首先会进入到mas_makeConstraints
方法,这是view
的分类方法,所以imageView
才能访问这个方法。这个方法最主要的功能是创建了一个maker
传递给block
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
//关闭自动布局
self.translatesAutoresizingMaskIntoConstraints = NO;
//创建maker
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
//添加约束
return [constraintMaker install];
}
block
依次执行left、top、equalTo、offset
,这里我看做有两个约束,left、top
。
第一个约束总是maker
去执行,而且每个约束最终都会走到MASConstraintMaker
的一个方法,一个创建MASConstraint
子类的工厂方法FUNC1
,重点分析(后面都叫FUNC1
):
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
//利用枚举值NSLayoutAttribute、self.view创建的MASViewAttribute
//其实也只是作为MASViewAttribute的两个属性值保存起来而已
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
//利用viewAttribute创建MASViewConstraint,其中只保存了FirstViewAttribute的值。
//FirstViewAttribute就是需要添加约束的一方的所有属性,SecondViewAttribute会在equalTo后得到
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;
// constraints 存放的是单次执行的结果
/*
* make.left.top.equalTo(self.view).offset(60);
* make.right.bottom.equalTo(self.view).offset(-60);
* 就是执行个数为两个,每次结果都是 MASCompositeConstraint
*
*/
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
里面的判断很有意思,会得到前面提到的,单个执行约束超过1个以上就会得到一个MASCompositeConstraint
对象,只有一个约束就是MASViewConstraint
对象。
make.left
,入参constraint = nil
,返回值是MASViewConstraint
make.left.top
,入参constraint = make.left = MASViewConstraint
,返回值就是MASCompositeConstraint
,MASCompositeConstraint.childConstraints = @[MASViewConstraint0 (left) ,MASViewConstraint1 (top)]
(每个约束对象都是MASViewConstraint),具体实现如下:
// MASConstraint.h
// 此处 self = make.left = MASViewConstraint
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
// MASViewConstraint.h
// 工厂方法FUNC1判断里面,maker作为了MASViewConstraint的代理,也作为了MASCompositeConstraint的代理
// 此处 self.delegate = MASConstraintMaker
// 所以又回到工厂方法FUNC1了,这也是链式语法串起来的意义
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
大胆假设make.left.top.right
,入参constraint = make.left.top = MASCompositeConstraint
, MASCompositeConstraint.childConstraints = @[MASViewConstraint0 (left) ,MASViewConstraint1 (top) ,MASViewConstraint2 (right)]
,返回值是MASViewConstraint
具体实现如下:
// MASConstraint.h
// self = make.left.top = MASCompositeConstraint
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
// MASCompositeConstraint.h
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
[self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
//返回自身
return self;
}
- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
// 还不明白为什么这样做,可能是怕释放掉
id<MASConstraintDelegate> strongDelegate = self.delegate;
// FUNC1 能找到maker作为了MASCompositeConstraint的代理
// self.delegate = MASConstraintMaker ,也回到了FUNC1
MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
// 此处代理是为后面的secondAttribute设置用的
newConstraint.delegate = self;
// 添加约束到数组,例子中是right的约束
[self.childConstraints addObject:newConstraint];
// 返回值是MASViewConstraint
return newConstraint;
}
四个约束可以自己尝试下。
3 、添加约束
equalTo
就比较简单,主要是添加secondViewAttribute、layoutRelation。
offset
也比较简单,是添加layoutConstant
所有约束的属性值都有了,就需要添加到对应的firstViewAttribute.view上去了,主要是调用到install
- (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;
}
可以看到install
的时候,会去遍历数组constraints
,而constraints
里面存放的是单次执行的MASConstraint
对象,前面有说到,单次执行是1个约束的时候得到的是MASViewConstraint
,单次执行1个约束以上的时候得到是MASCompositeConstraint
。
其实最终都会走向MASViewConstraint
的install方法。这个方法就是先拼装出一个NSLayoutConstraint
对象,然后添加到对应的View
。
自此,简单的Masnory使用就分析完了,后面还会补充其他部分。