在ios开发中,Masonry
是最常用的第三方开发布局框架。Masonry
是基于自动布局技术实现的,所以说Masonry
是NSLayoutConstrait
的简易封装版本,底层还是封装系统的NSLayoutConstraint
来实现的。下面的代码就是一段典型的布局代码:
[self.bgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(@10);
make.left.right.bottom.mas_equalTo(self);
}];
基于以上代码,从源码分析看这个框架是如何实现自动布局的,以及如何实现链式编程的。
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
mas_makeConstraints执行流程
1.Autoresizing
有可能会和autolayout
产生冲突,所以第一件事情就是关闭Autoresizing
。
2.紧接着会创建一个constraintMaker
,可以把它称之为约束制造者。MASConstraintMaker
的初始化方法如下,
- (id)initWithView:(MAS_VIEW *)view {
self = [super init];
if (!self) return nil;
self.view = view;
self.constraints = NSMutableArray.new;
return self;
}
约束制造者内部会绑定目标view,并且生成一个可变数组用来保存约束constraint。
3.执行传入的block更新具体的约束信息。
4.让约束制造者安装约束[constraintMaker install]
。安装的步骤是放在block执行之后,这也很好理解,需要外界更新了具体的约束,约束制造者才能够去安装约束。
[constraintMaker 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;
}
这里面主要做了两件事情,清空之前的约束,将block执行时更新的约束install
一遍。就是清楚老约束,安装新约束。 MASConstraint
的install
实例方法就是具体的添加约束的逻辑,这个部分的代码简单理解他的工鞥就是封装系统的NSLayoutConstraint
,实现添加自动布局的约束的逻辑。
以上就是使用Masonry给一个视图添加约束的大致步骤,这么看来逻辑并不是很复杂。但这不是Masonry的重点,接下来来看一下约束制造者是如何更新约束的make.left.right.bottom.equalTo(self);
。
//添加一个left约束属性
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
//self就是约束制造者,self的view就是绑定的视图。以下两行实现了给视图绑定约束属性。
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
//此时传进来的是nil,先跳过暂不考虑,当使用了多个点语法时,会进入这个判断
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;
}
//设置代理,constraints数组保存约束属性
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
从上述注释逻辑来看,每次调用left就是将约束添加到约束制造者的数组中。
但这句代码make.left.right.bottom.equalTo(self)
是可以一直使用点语法点出接下来的right等方法,其实这就是链式编程在oc语法中的具体实现。为什么能够这样实现呢,以right为例可以看一下right的代码:
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
它的返回值其实就是self,所以在调用了left
之后,还是可以继续调用right
和bottom
,相当于把left
,right
,bottom
三个约束更新到了约束数组中。这里也可以看出来链式编程的一个特点,方法返回值必须包含方法调用者。
接下来再来看一下equalTo
的实现:
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
从make.left.right.bottom.mas_equalTo(self)
也可以看出,equal_to
接的是()
,表示block
的执行,它的返回值是一个block
。name
这个block
后面还能够继续点出来么,答案是可以的。因为这个block
的返回值还是self
,仍然是方法的调用者。
Masonry
中其他的更新功能大致逻辑与上面的类似,以上简要概括了约束的产生和装载的整个过程,并通过Masonry
对链式编程有了了解。