美文网首页
Masonry原理与链式编程

Masonry原理与链式编程

作者: 强布斯 | 来源:发表于2018-08-19 23:38 被阅读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对点语法有了了解。
    世界和平万岁!

    相关文章

      网友评论

          本文标题:Masonry原理与链式编程

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