51

源码解读——Masonry

 4 years ago
source link: https://www.tuicool.com/articles/32uemeu
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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 架构

基本组成

2aY7ZzB.png!web

Masonry 主要方法由上述例子就可一窥全貌。Masonry 主要通过对 UIViewNSView )、 NSArrayUIViewController 进行分类扩展,从而提供自动布局的构建方法。相关方法定义在上图所示部分文件中:

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 的一系列方法及其子类(包括: MASCompositeConstraintMASViewConstraint ),从而实现约束的创建与添加。

MASConstraint 则提供了一系列返回类型为 MASConstraint 的方法,从而实现了链式 DSL,使 Masonry 具备了简洁灵活的优点。

下面,我们依次来介绍 Masonry 框架中的几个重要类:

MASLayoutConstraint
MASViewAttribute
MASConstraint
MAConstraintMaker

MASLayoutConstraint

MASLayoutConstraint 类继承自 NSLayoutConstraint 类。相比其父类,它就多了一个属性 mas_key

MASLayoutConstraint 用来表示 布局约束

MASViewAttribute

我们知道在自动布局系统中,约束的本质是一个方程式:

item1.attribute1 = multiplier × item2.attribute2 + constant

yIZ7V3b.png!web

MASViewAttribute 就是约束方程式中一个 itemattribute 组成的单元。

如下所示便是 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 是一个抽象类,主要为其子类 MASViewConstraintMASCompositeConstraint 声明了一些共有的方法。 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

MASViewConstraintMASConstraint 的子类,可以称之为 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 () <MASConstraintDelegate>

@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<MASConstraintDelegate> strongDelegate = self.delegate;
    MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
    newConstraint.delegate = self;
    [self.childConstraints addObject:newConstraint];
    return newConstraint;
}

可以看出,该方法内部将通过其代理新创建的普通约束或组合约束添加至 MASCompositeConstraintchildConstraints 数组中,并设置子约束的代理为 MASCompositeConstraint 的代理。

事实上,在 Masonry 中,下文将要提到的 MASConstraintMaker 充当了所有约束的最终代理,如下图所示。 MASCompositeConstraint 只是充当了转接和补充的作用。

e6fmqmQ.png!web

至于关系操作方法、倍数操作方法、常量操作方法、优先级操作方法所调用的抽象方法。 MASCompositeConstraint 对此的实现基本相同,都是对 childConstraints 中的约束进行遍历设置。

MASConstraintMaker

MASConstraintMaker 是 Masonry 的核心。

MASConstraintMaker 指定了构建布局的目标视图以及相关的约束。

@interface MASConstraintMaker () <MASConstraintDelegate>

@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);

上面提到, MASViewConstraintMASCompositeConstraint 都会利用其代理来创建并添加约束项,而它们的代理都是 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];
}

方法内部先设置 translatesAutoresizingMaskIntoConstraintsNO 。因为,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 ,本质上还是调用 MASViewConstraintinstall 方法。该方法根据 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,极大地提升了自动布局开发的效率。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK