美文网首页
Masonry源码阅读

Masonry源码阅读

作者: Sweet丶 | 来源:发表于2021-03-23 14:15 被阅读0次

在项目中使用Masonry让我们做屏幕适配变得简单,下面来通过源码了解下它。

一、点链式语法怎么实现的
UIEdgeInsets padding = UIEdgeInsetsMake(10, 15, 10, 15);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
      make.edges.equalTo(superview).with.insets(padding);
}];

1> .语法是相当于调用getter方法,.edges就相当于调用了edges方法,返回值是MASConstraint *类型的.
2> 接下来的.equalTo(superview),首先是点语法优先级高于(),所以是先执行.equalTo,调用MASConstraintequalTo方法,返回的是一个block,之后小括号()是进行block调用,返回值也是MASConstraint *
3> 接下来调用.with, 返回的self也是MASConstraint *
4> 接下来调用.insets,原理跟equalTo是一样的.

二、约束是怎么添加到view上去的
1. 约束的添加:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
// 1. 创建MASConstraintMaker对象
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
// 2. 调用block,会执行外部写在block中的内容, 将view的约束先集成在constraintMaker
    block(constraintMaker);
// 3. constraintMaker调用install,将集成的约束设置到view中
    return [constraintMaker install];
}

我们写在blcok中的一个链式调用,作用就是会根据调用的方法创建一个对应的MASViewConstraint对象。比如

  • make.left
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
// 上面方法会调用这里
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
// 上面方法会调用这里
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
// 1.根据layoutAttribute创建MASViewAttribute
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
// 2.根据viewAttribute作为约束的FirstViewAttribute创建MASViewConstraint
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        // 4.如果调用到这里时constraint已经有值,则创建compositeConstraint组合已经存在的MASViewConstraint
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    if (!constraint) {
// 3.链式调用的初始调用会来到,将newConstraint添加到数组中
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}
  • make.left.right是在上一步的基础上做了什么?
// MASConstraint类中
- (MASConstraint *)right {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}

// MASViewConstraint类中的
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    // self.delegate是初始化的时候设置的MASConstraintMaker
// 所以这里是调用MASConstraintMaker中的constraint:addConstraintWithLayoutAttribute:会创建MASCompositeConstraint,见上面make.left的代码
    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
  • make.left.right.equalTo(self.view)在上一步基础上做了什么?
// self.view就是传进去的attribute参数
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}
// attribute是self.view, 由下面可知是将self.view和layoutRelation赋值给属性
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        self.layoutRelation = relation;
        self.secondViewAttribute = attribute;
        return self;
    };
}
// 重写了secondViewAttribute的setter方法
- (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.right.equalTo(self.view).with是为了看起来连贯吧
- (MASConstraint *)with {
    return self;
}
  • make.left.right.equalTo(self.view).with.offset(0);在上一步基础上做了什么
//将值赋值给属性offset, 也就是属性layoutConstant
- (MASConstraint * (^)(CGFloat))offset {
    return ^id(CGFloat offset){
        self.offset = offset;
        return self;
    };
}
- (void)setOffset:(CGFloat)offset {
    self.layoutConstant = offset;
}

上面是其中一个链式调用后产生的效果,block中其他几个链式也是一样的。接下来看[constraintMaker install]

// 遍历constraints中的MASViewConstraint,逐个调用install,调用完之后从maker中移除constraints
- (NSArray *)install {
    if (self.removeExisting) {// 如果是remake,则会来这里,逐个移除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;
}

// MASConstraint类中的install方法
- (void)install {
   // 1. 取出约束的firstLayoutItem和secondLayoutItem
    UIView *firstLayoutItem = self.firstViewAttribute.item;
    NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
    UIView *secondLayoutItem = self.secondViewAttribute.item;
    NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;

    // 2. 如果没有设置secondLayoutItem或者secondLayoutAttribute,则这里默认为superview的对应LayoutAttribute
    // 比如 make.left.equalTo(@10)
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }
    // 3. MASLayoutConstraint是继承NSLayoutConstraint的,这里调用系统的方法添加约束,
    MASLayoutConstraint *layoutConstraint
        = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                        attribute:firstLayoutAttribute
                                        relatedBy:self.layoutRelation
                                           toItem:secondLayoutItem
                                        attribute:secondLayoutAttribute
                                       multiplier:self.layoutMultiplier
                                         constant:self.layoutConstant];
    // 4.设置约束的优先级
    layoutConstraint.priority = self.layoutPriority;
    layoutConstraint.mas_key = self.mas_key;
    // 5.找出约束添加到哪个view上,并赋值给self.installedView
    if (self.secondViewAttribute.view) {// 第一第二个view最近的一个superView
        UIView *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
        self.installedView = closestCommonSuperview;
    } else if (self.firstViewAttribute.isSizeAttribute) {
        self.installedView = self.firstViewAttribute.view;
    } else {
        self.installedView = self.firstViewAttribute.view.superview;
    }

    // 6.查找已存在的相似的约束
    MASLayoutConstraint *existingConstraint = nil;
    if (self.updateExisting) {// 使用mas_updateConstraints时才来这里
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    }
    if (existingConstraint) {
        // 6.1 如果这个约束关系已经存在则只更新constant
        existingConstraint.constant = layoutConstraint.constant;
        self.layoutConstraint = existingConstraint;
    } else {
        // 6.2 如果这个约束关系之前是没有的,则添加到view上,
        [self.installedView addConstraint:layoutConstraint];
        self.layoutConstraint = layoutConstraint;
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}

以上就是约束从创建到添加到view的完整过程。同时由上面源码可以知道:

1> .equalTo(view)中的view如果是superView的话是可以省略的,默认就是superview。
2> .equalTo(view.mas_top), 也是可以只写.equalTo(view),默认就是与第一个view相同的layoutAttribute。

2.约束的update

只适合于个别约束关系更改constant值时使用。相比mas_makeConstraints多了constraintMaker.updateExisting = YES; 由上面的代码可知,如果是已存在的约束则更新constant值,如果不存在则添加一个新约束关系,这就有可能和原本的约束冲突。

// 
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    constraintMaker.updateExisting = YES;
    block(constraintMaker);
    return [constraintMaker install];
}
3.约束的remake

由下面代码可知只比mas_makeConstraints多了constraintMaker.removeExisting = YES;作用就是在重新添加约束前,移除之前的。

- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    constraintMaker.removeExisting = YES;
    block(constraintMaker);
    return [constraintMaker install];
}
三、如果使用.edges.size.center是怎么添加约束的
- (MASConstraint *)edges {
    return [self addConstraintWithAttributes:MASAttributeTop | MASAttributeLeft | MASAttributeRight | MASAttributeBottom];
}

- (MASConstraint *)size {
    return [self addConstraintWithAttributes:MASAttributeWidth | MASAttributeHeight];
}

- (MASConstraint *)center {
    return [self addConstraintWithAttributes:MASAttributeCenterX | MASAttributeCenterY];
}

答案是会创建MASCompositeConstraint,而MASCompositeConstraint类的install也是逐个添加到约束的

// MASCompositeConstraint类的install
- (void)install {
    for (MASConstraint *constraint in self.childConstraints) {
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
}
四、这里添加约束的block为什么不会循环引用

block捕获局部变量的原理决定了,如果block中使用强引用的外部变量,会增加对象的引用计数,但是由上面源码可以知道,这个block是没有被view持有的,没有循环引用,block在mas_makeConstraints:方法调用完之后会释放,block释放后也就移除了对外部变量的引用。

五、注意点
  1. 更新约束时选择update还是remake
    如果是约束关系不变,只是更新约束的数值,就用update;如果是要改变约束关系则必须用remake。

  2. equalTomas_equalTooffsetmas_offset

#define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))

mas_equalTo也是调用equalTo方法,只不过参数会包装成MASBoxValue后作为equalTo的参数,这样就可以传float等数据类型了,mas_offset同理。

  1. 如果不写跟哪个view的关系,默认是与superview之间的约束。

  2. 添加约束的代码不要放在layoutSubviews方法。
    会重复添加相同的约束关系到view上,消耗性能,因为layoutSubviews方法是每次superview的bounds发生变化时,就会调用一次,而约束只需要添加一遍。官方的说明是只有当约束不能很好的满足要求时候才应该在这个方法里面设置frame。
    You should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want. You can use your implementation to set the frame rectangles of your subviews directly.

  3. 添加与view的关系时需要.mas_left而不是.left

- (MASViewAttribute *)mas_left {
    return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeft];
}

.mas_left是创建了一个MASViewAttribute,作为约束的secondViewAttribute。

  1. 如果是要根据字体进行自动放大label,可以使用.greaterThanOrEqualTo.lessThanOrEqualTo或者不添加对应方向上的约束。

  2. 以下几个写法是等价的, 都是相对于父视图left有10的间距。

make.left.equalTo(@10);
make.left.mas_equalTo(10);
make.left.offset(10);
make.left.mas_offset(10);

相关文章

  • Masonry 介绍 2018-01-29

    介绍 Masonry 源码:https://github.com/Masonry/Masonry Masonry是...

  • Masonry源码阅读

    Masonry源码阅读 AutoLayout是Apple在iOS6中新增的UI布局适配的方法,用来替代iOS6之前...

  • Masonry 源码阅读

    前言 Masonry 是 Objective-C 中用于 AutoLayout (see Understandin...

  • Masonry源码阅读

    在项目中使用Masonry让我们做屏幕适配变得简单,下面来通过源码了解下它。 一、点链式语法怎么实现的 1> .语...

  • 关于Masonry小记

    Masonry 源码:https://github.com/Masonry/Masonry Masonry是一个轻...

  • Masonry的用法

    Masonry 源码:https://github.com/Masonry/Masonry; 看一下Masonry...

  • Masonry学习报告

    Masonry 源码:https://github.com/Masonry/Masonry 如果是使用cocoa ...

  • Masonry使用归纳总结

    前言 Masonry 源码:https://github.com/Masonry/MasonryMasonry是一...

  • iOS Masonry 源码阅读

    Masonry 源码阅读 阅读源码是一种美妙的体验 这么强大的布局库,就不做解释了。因为系统的自动布局写起来很麻烦...

  • 第三方Masonry-实现纯代码自动布局(1)

    Masonry 源码:https://github.com/Masonry/Masonry 使用第三方先看看Mas...

网友评论

      本文标题:Masonry源码阅读

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