Masonry实现原理并没有那么可怕

本文深入解析了Masonry库的工作原理,包括约束创建、链式调用实现及安装过程。通过具体代码示例,展示了如何利用Masonry进行简洁高效的UI布局。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

想必在AFNetworking之后,Masonry成了广大iOS开发者日常开发不可或缺的三方库之一。它的使用真的非常简单,例如:

[greenView makeConstraints:^(MASConstraintMaker *make) {
        make.top.greaterThanOrEqualTo(superview.top).offset(padding);
        make.left.equalTo(superview.left).offset(padding);
        make.bottom.equalTo(blueView.top).offset(-padding);
        make.right.equalTo(redView.left).offset(-padding);
        make.width.equalTo(redView.width);

        make.height.equalTo(redView.height);
        make.height.equalTo(blueView.height);
        
    }];
复制代码

从这段短小精悍的代码中,我们已经能够挖掘出它背后的原理。本文假设你对Masonry已经有基本的了解和使用。

首先我们提取一些“关键词”,他们究竟是啥意思?

1.makeConstraints:

2.make

3.left、right、height...

4.equalTo()

1.makeConstraints:

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}
复制代码
  1. MASConstraintMaker被传入了block()中,即MASConstraintMaker实例就是我们的第二个“关键词”---make。从[constraintMaker install]还可看出,make负责了约束的添加。后文详细介绍make

  2. translatesAutoresizingMaskIntoConstraints,官方解释大致为:默认情况下它是YES,即viewautoresizing mask会自动成为它的布局。如果我们希望手动布局,需要将它设为NO

2.make(MASConstraintMaker)

从链式调用make.top.equalTo...,我们可以看出,make中定义了这些约束属性,它们是这样实现的:

// step 1
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

// step 2
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

// step 3
- (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]) {
        //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;
}
复制代码
  1. MASViewAttribute 从实现文件中得知,MASViewAttribute = View + NSLayoutAttribute + item,这是一个可变元组,存储了View和与它相关的约束信息。

  2. MASViewConstraint 这就是一个约束,它包含firstViewAttributesecondViewAttribute

  3. 由step2可知,单纯的约束属性在该方法下的第一个参数都是nil,所以我们先直接看这种情况下的step3的执行情况。它被加入了一个约束数组中。

if (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
复制代码

4)链式调用的实现 我们知道Masonry链式调用是它广受好评的优点之一。那么,是如何做到make.top.left这样的操作呢? 由step3得知,make.top的返回类型是MASViewConstraint,那MASViewConstraint中是如何调用到left的呢? 从MASViewConstraint的父类MASConstraint可以看到,这里也定义了所有的布局属性,而这些布局属性的实现方式,如下,例:

// MASConstraint.m
- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}

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

    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
复制代码

MASViewConstraint把这个方法的具体实现委托给了代理方法。而在step3中,这个代理正是MASConstraintMaker。不同的是,此时,step2方法的参数不在是nil了,而是当前约束属性。这也是的step3的处理逻辑不一样了。(回到step3中再看看!)此时,MASCompositeConstraint登场了,它是一个约束组合。以make.top.left...为例,这个约束组合包含了topleft,并且调用了shouldBeReplacedWithConstraint:方法,如下:

- (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.top.left...为例,这个方法找到了之前存储top约束的位置,并替换成了约束组合。最终,topleft就一起被加入和make的约束数组中。

3.install

从上文看,我们已经拿到了所有的约束。这些约束是如何加入到视图上的?来看看install方法。 摘录一下MASConstraintMaker中的install

- (NSArray *)install {
    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) {
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
    [self.constraints removeAllObjects];
    return constraints;
}
复制代码

make的处理相对简单,再看看MASViewConstraint本身是如何install的。由于方法太长,我不粘贴完整的代码了(分开来会比较容易看懂)。大致的流程如下: 1.如何约束是设置widthheight,这是视图自身的属性,则把当前视图的父视图作为关联视图(代码中为secondLayoutItem)。即这两个约束是相对于父视图设置的。

2.如果布局上存在相对视图,即通常写法中的equalTo(someView.mas_top)这样,则找到这2个视图最近的公共父视图,并把约束添加在这个父视图上。代码如下:

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;
    }
复制代码

3.最后,如果是更新约束操作,则找出需要更新的约束单独修改;否则,为视图添加约束,并记录在mas_installedConstraints中。

到这里,约束的添加已经完全梳理明白了。

4.equalTo()

还是得看具体实现方法:

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if ([attribute isKindOfClass:NSArray.class]) {
            NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
            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");
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}
复制代码

这里就比较好理解了,如果传入的是约束数组,则把他包装成MASCompositeConstraint,以同样的方式加入到makeconstraints中。如果是单个约束,则将其设为secondViewAttribute,用于install的时候使用。

总结

1.MASConstraintMaker作为工厂,生产一个个MASViewConstraint约束对象。 2.MASViewConstraintMASCompositeConstraint继承于抽象类MASConstraint,为我们提供了高度封装的约束对象 3.View+MASAdditions这个UIView的扩展是Masonry与外界交互的接口类,这样很好的把复杂的约束逻辑封装在内部管理,又提供了简单的API供用户使用。

你看懂了嘛~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值