[Masonry] 实现原理及链式调用分析

2019-08-21  本文已影响0人  luonaerduo

Masonry is a light-weight layout framework which wraps AutoLayout with a nicer syntax. Masonry has its own layout DSL which provides a chainable way of describing your NSLayoutConstraints which results in layout code that is more concise and readable. Masonry supports iOS and Mac OS X.

翻译:

Masonry是一个轻量级的布局框架,它使用更好的语法包装AutoLayout。 Masonry有自己的布局DSL,它提供了一种链式调用的方式来描述NSLayoutConstraints,从而使布局代码更简洁,更易读。 Masonry支持iOS和Mac OS X.

什么是DSL

DSL(Domain Specific Language) 翻译成中文就是:“领域特定语言”。首先,从定义就可以看出,DSL 也是一种编程语言,只不过它主要是用来处理某个特定领域的问题。

下边介绍iOS中如何实现链式调用的DSL。

为什么需要使用Masonry

首先看下直接用NSLayoutConstraints方式布局视图需要什么操作:

例如:我们需要布局一个视图view1,使他距离父视图上下左右都为10,NSLayoutConstraints布局代码如下:

公式:view1.top = superview.top * 1.0 + 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], ]];
Masonry代码如下:

[view1 mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.mas_equalTo(superview).with.insets(padding);}];
由此可以看到使用NSLayoutConstraints方式布局代码及其冗余且不易读。

Masonry框架结构分析

主要的几个类:

View+MASAdditions.h

MASConstraintMaker

MASViewConstraint

masonry_struct_chart

从调用mas_makeConstraints方法说起

首先我们看一个简单调用的例子:

[view1 mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(superview).offset(10)}];
mas_makeConstraints:实现如下:首先将self.translatesAutoresizingMaskIntoConstraints置为NO,关闭自动添加约束,改为我们手动添加,接着创建一个MASConstraintMaker对象,通过block将constraintMaker对象回调给用户,让用户对constraintMaker对象的属性进行初始化,其中block(constraintMaker)就相当于我们直接在该方法内部调用make.left.mas_equalTo(superview).offset(10),然后调用install方法对约束进行安装,该方法返回一个数组,数组当中存放约束数组,成员类型为MASViewConstraint

make.left.mas_equalTo(superview).offset(10)
点击去看下.left的调用实现:

MASViewConstraint是对NSLayoutConstraint的封装,最后将布局约束添加到一个数组当中

block回调执行完毕之后,最后对布局进行安装[constraintMaker install],该方法,最后会调用到MASViewConstraint当中的install方法,其中有一个比较重要的方法:

MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];

遇到的问题

MAS_SHORTHAND 和 MAS_SHORTHAND_GLOBALS的含义

MAS_SHORTHAND: 如果我们用Masonry框架不想写mas_前缀,需要在导入头文件之前定义这个宏

MAS_SHORTHAND_GLOBALS: 定义这个宏,可以让equalTo接受基本数据类型,内部会对基本数据类型进行封装

//define this constant if you want to use Masonry without the 'mas_' prefix#define MAS_SHORTHAND
//define this constant if you want to enable auto-boxing for default syntax#
define MAS_SHORTHAND_GLOBALS#import "Masonry.h"
Convenience initializer 和 designated initializer

在我们阅读masonry源码的过程中,我们发现有两个初始化方法,注释不太一样,位于MASViewAttribute类下:

/** * Convenience initializer. */

designated initializer 只有一个,它会为 class当中每个 property 都提供一个初始值,是最完整的 initWith 方法,例如我们在masonry当中也可以看到:

convenience initializer 则可以有很多个,它可以选择只初始化部分的 property。convenience initializer 最后到会调用到 designated initializer,所以 designated initializer 也可以叫做 final initializer

NS_NOESCAPE修饰符

在masonry源码当中,我们看到在修饰block的时候用到了NS_NOESCAPE

masonry为什么不会引起循环引用

比如我们可能经常会写如下代码:

[self.view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.superview).offset(10);
}];
这里为什么不需要写@weakify(self),接着看mas_makeConstraints:是如何实现的:

链式调用实战应用

在我们开发过程中,我们会经常用到UILabel,每次初始化都要设置一堆的属性,比较麻烦,当然我们也可以采取类似如下方法:+ (UILabel *)createLabelWithFont:(UIFont *)font andTextColor:(UIColor *)color andDefaultContent:(NSString *)content,但是一旦我们所需要初始化的参数比较多时,就会造成方法参数非常多,甚至我们有些参数根本不需要初始化,用链式编程该如何实现呢??

首先为UILabel创建一个category,#import “UILabel+zjLabel.h”,代码如下:

import "UILabel+zjLabel.h"

@implementation UILabel (zjLabel)

UILabel *label = [UILabel zj_createLabel:^(UILabel * _Nonnull label) {
label.zj_text(@"haha").zj_font([UIFont systemFontOfSize:24]).zj_textColor(UIColor.redColor);
}];
[superview addSubview:label];
不需要初始化的参数可以直接不写,只初始化我们需要的

总结

另外很多人担心自动布局的性能问题,事实上苹果已经在iOS12中对auto layout进行优化:

WWDC2018讲解了iOS12优化后的表现

auto_layout_ optimize

可以看到在iOS12之前auto layout性能会随着嵌套视图的增加呈指数增长,但是在iOS12上苹果官方已经对此进行了优化,随着嵌套视图的增加性能问题得到了大幅的提升。

链式编程的特点:方法返回值是block,而且该block必须有返回值,返回值就是对象本身,block也可以输入参数

上一篇下一篇

猜你喜欢

热点阅读