Masonry原理与链式编程

2018-08-19  本文已影响0人  强布斯

在iOS开发中,Masonry是我们常用的一个轻量级的布局框架,它以链式语法的形式优雅的实现了自动布局。
下面我们以一个典型的布局代码为入口来剖析Masonry

[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(100.f, 100.f));
        make.left.top.offset(@20.f);
}];

通过 mas_makeConstraints 把我们添加的约束写在其提供的block内,非常方便的设置约束关系。可以想象,万变不离其宗,Masonry一定是封装了系统的NSLayoutConstraint。

一、Masonry中的那些类

1、Masonry类关系图

masonry类结构图.png

2、核心类的说明

  1. View+MASAdditions:是方便我们对UIView添加约束所做的一个UIView的分类,里面包括了我们最常用的 mas_makeConstraints、mas_updateConstraints、mas_remakeConstraints 3个方法和一些在添加约束过程中用到的属性。
  2. MASConstraint:Masony中约束的基类,封装了Masonry约束的通用属性和方法。Masonry不直接使用此类,而是使用它的子类 MSAViewConstraint和MASCompositeConstraint。
  3. MSAViewConstraint:对NSLayoutConstriant的封装,负责最终产生NSLayoutConstriant对象,并将该对象加载到目标view。
  4. MASCompositeConstraint:对MSAViewConstraint
    的组合,以数组的形式来维护一组约束。当使用诸如 make.left.right 会产生一个约束组合以 MASCompositeConstraint 的形式来管理。
  5. MASConstraintMaker:一个产生约束的工厂类,也就是我们在block中所使用的make,核心工作是产生约束和装载约束。
  6. MASViewAttribute:对产生 NSLayoutConstraint 所需元素的封装,包括view、layoutAttribute等关键元素。
  7. MASLayoutConstraint :继承于 NSLayoutConstraint ,只是增加了一个标识约束的mas_key,用于方便区分约束。

二、核心源码解析

我们从入口方法 mas_makeConstraints 入手,来看看Masonry是如何把约束加载到目标view上的
1、mas_makeConstraints

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    // 关闭自动添加约束,使手动添加的约束生效
    self.translatesAutoresizingMaskIntoConstraints = NO;
    // 创建约束工厂
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    // 通过block回调,用工厂去创建并记录约束
    block(constraintMaker);
    // 安装约束并返回约束数组
    return [constraintMaker install];
}

该方法首先生成一个maker工厂对象,接着把该对象作为参数传递到我们设置约束的block中,也就是说用这个工厂对象来制造约束,并在最后用install方法来装载所有的约束。
2、make.left 约束的产生

  1. 在 MASConstraintMaker 中找到left的getter方法
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
  1. 调用方法 addConstraintWithLayoutAttribute,
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
  1. 接着进入 constraint: addConstraintWithLayoutAttribute: ,注意此时第一个参数constraint为nil,
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    // 创建MASViewAttribute
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    // 根据 viewAttribute 创建 MASViewConstraint
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    // 当链式语法第一次创建约束时候(make.left),contraint为nil
    // 第二次(make.left.right),constraint存在,那么组合成MASCompositeConstraint对象
    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;
}

这是一个生产约束的核心方法:
到这里我们的make.left就成功产生了一个约束对象,并将该约束对象作为函数的返回值return。
可以看到当 constraint 为 nil 时,产生的约束(MASViewConstraint)被直接添加进 constraints 数组中;
当 constraint 存在时,约束被组合成 MASCompositeConstraint 。
并且以上两种情况约束的代理都被设置成了self(MASConstraintMaker),那我们进入 MASViewConstraint 约束对象中来看看设置这个代理究竟有什么用,

  1. 进入约束对象 MASViewConstraint, 查看 make.left.right 是如何产生第二个约束right的
    从上面可知调用make.left会得到一个 MASViewConstraint 对象,那么make.left.right 产生第二个约束也就是要从第一个约束入手,找到 MASViewConstraint 的基类方法
- (MASConstraint *)right {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}

然后进入 addConstraintWithLayoutAttribute

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");

    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}

从这里我们看到了在产生约束时设置的 delegate ,也就是 MASConstraintMaker 工厂对象,到这里就不用再多赘述了,简单来说就是 maker 产生一个新约束(right)和第一个约束(left)组合成了一个 MASCompositeConstraint 组合约束对象。

  1. equalTo方法
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

因为内部较繁琐,但不复杂,所以在此就不展开来剖析。总结来讲就是对 MASViewConstraint 或者 MASCompositeConstraint 对象设置 relation 关系(equal)和具体的约束值(@50)。

  1. nstall方法 装载约束
    maker的install方法
 // MASConstraintMaker
- (NSArray *)install {
    // 是否移除原有约束
    if (self.removeExisting) {
        // 获取当前view的所有约束
        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;
}

由maker的install方法调用具体约束的install方法

// MASViewConstraint
- (void)install {
    // 判断约束是否被装载过
    if (self.hasBeenInstalled) {
        return;
    }
    
    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;
    }
    // 产生 MASLayoutConstraint (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;
    
    // 查找装载约束的View
    // 如果 secondView 存在那么递归查找最近的公共父视图
    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];
    }
    // 装载约束到目标View
    if (existingConstraint) {
        // just update the constant
        existingConstraint.constant = layoutConstraint.constant;
        self.layoutConstraint = existingConstraint;
    } else {
        [self.installedView addConstraint:layoutConstraint];
        self.layoutConstraint = layoutConstraint;
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}

最后调用 install 装载约束,到此为止约束就被添加到view上了。

三、链式语法浅析

  1. MASConstraintMaker 的left方法为例
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

left 方法返回的是一个约束对象,由这个约束对象又可以继续“点”产生第二个约束,这样就实现了连续点。

  1. MASConstraint 的offset方法
- (MASConstraint * (^)(CGFloat))offset {
    // 匿名block,以CGFloat为参数,并返回MASConstraint来支持链式调用
    return ^id(CGFloat offset){
        self.offset = offset;
        return self;
    };
}

综上可知,能连续点的条件是“点语法”返回的对象能继续使用“点语法”。

四、结束语

以上,通过一个Masonry布局语句,追根溯源,简要概括了约束的产生和装载的整个过程,并通过Masonry对点语法有了了解。
世界和平万岁!

上一篇下一篇

猜你喜欢

热点阅读