美文网首页
源码解读——Masonry

源码解读——Masonry

作者: baochuquan | 来源:发表于2019-10-02 12:02 被阅读0次

    原文链接

    Masonry 概述

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

    基本组成

    image

    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
    
    image

    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 只是充当了转接和补充的作用。

    image

    至于关系操作方法、倍数操作方法、常量操作方法、优先级操作方法所调用的抽象方法。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,极大地提升了自动布局开发的效率。

    参考

    1. Masonry

    (完)

    相关文章

      网友评论

          本文标题:源码解读——Masonry

          本文链接:https://www.haomeiwen.com/subject/oxalpctx.html