Masonry 源码解析

Masonry 是一个轻量级的用于自动布局(AutoLayout)的第三方框架,以其简洁的使用方式,受到广大开发者的青睐。本篇文章将带你一步步的去了解其实现原理,知其所以然!

结构概览

  • 最上面的几个 category,包含了我们常用的一些方法及属性,例如:

    - (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
    
  • 中间的是一个继承自 NSObject 的工厂类,主要负责创建 MASConstraint 对象以及把约束添加到视图上。

  • 最下面 MASConstraint 是个抽象类,其中有很多的方法都必须在子类中重写。MASViewConstraintMASCompositeConstraint 是它的两个子类,介绍这两个之前我们先说下 MASViewAttribute

    我们都知道系统创建一条约束的方法:

    +(instancetype)constraintWithItem:(id)view1
                        attribute:(NSLayoutAttribute)attr1
                        relatedBy:(NSLayoutRelation)relation
                           toItem:(nullable id)view2
                        attribute:(NSLayoutAttribute)attr2
                       multiplier:(CGFloat)multiplier
                         constant:(CGFloat)c;
    

    MASViewAttribute 就是对 attributeItem 这两个属性的封装;MASViewConstraint 就是对 MASViewAttribute 的封装,可以理解为一条约束对象;MASCompositeConstraint 则就是约束的集合,它里面有个私有的数组用来存放多个 MASViewAttribute 对象。

源码分析

View+MASAdditions

我们绘制一个居于父视图(self)上、左为 20.0f ,右为 -20.0f并且高度一半的 view 的约束大概是这样的:

[view mas_makeConstraints:^(MASConstraintMaker *make) {
    make.height.equalTo(self).multipliedBy(0.5);
    make.top.equalTo(self).offset(20.0f);
    make.left.equalTo(@20.0f);
    make.right.offset(-20.0f);
}];

我们点进 View+MASAdditions.m 里面可以看到内部:

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}
  • 首先这里已经帮我们把 translatesAutoresizingMaskIntoConstraints 属性设置为 NO 了,这样我们在外面可以省去这一步。
  • 然后初始化 MASConstraintMaker 工厂实例对象并保存了当前视图 self.view
  • 接着把初始化好的 MASConstraintMaker 对象传入 block,回调给外面配置约束属性。
  • 最后调用 install 方法,把配置好的约束添加到视图上去。

以上就是添加约束的大概流程,我们再看看更新重新构建约束的方法,也就是:

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

- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    constraintMaker.removeExisting = YES;
    block(constraintMaker);
    return [constraintMaker install];
}

我们可以发现它们和 mas_makeConstraints 唯一的区别在于多传了 updateExisting 以及 removeExisting 这两个 BOOL属性值:

  • mas_updateConstraints:找到需要更新的 NSLayoutConstraint,替换成新约束。
  • mas_remakeConstraints:清除所有 NSLayoutConstraint,再添加新约束。

MASConstraintMaker

知道了这三个方法的大概作用和关系,我们来详细看看 MASConstraintMaker 这个工厂类是如何配置约束的:

make.height

调用链如下:

- (MASConstraint *)height {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}

- (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;
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

由于 constraint 传的是 nil,所以我们先忽略中间一段代码:

  • 这里先是初始化了 MASViewAttribute 对象并保存了 viewitem以及NSLayoutAttribute三个属性。
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
    return self;
}

- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [super init];
    if (!self) return nil;
    
    _view = view;
    _item = item;
    _layoutAttribute = layoutAttribute;
    
    return self;
}
  • 然后又初始化了 MASViewConstraint 对象,内部配置了些默认参数并保存了第一个约束参数 MASViewAttribute
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
    self = [super init];
    if (!self) return nil;
    
    _firstViewAttribute = firstViewAttribute;
    self.layoutPriority = MASLayoutPriorityRequired;
    self.layoutMultiplier = 1;
    
    return self;
}
  • 最后设置 MASViewConstraint 对象代理并添加到一开始准备好的 self.constraints 数组中,返回。

这些工作就是在输入 make.height 进行的全部工作,它会返回一个 MASViewConstraint 对象,用于之后的继续配置。

MASViewConstraint

make.height.equalTo(self)

make.height 返回 MASViewConstraint 对象后,会继续在这个链式的语法中调用下一个方法来指定约束的关系。

- (MASConstraint * (^)(id attr))equalTo;

- (MASConstraint * (^)(id attr))greaterThanOrEqualTo;

- (MASConstraint * (^)(id attr))lessThanOrEqualTo;

文章开头说过,MASConstraint 是个抽象类,具体实现都在它的两个子类中,equalTo(self) 的调用链如下:

//MASConstraint.m

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

//MASViewConstraint.m

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if ([attribute isKindOfClass:NSArray.class]) {
            .....
        } else {
            .....
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}

这里同样先省略部分代码,方便我们阅读:

  • 首先是 self.layoutRelation 保存了约束关系且重写了 set 方法,在里面用 self.hasLayoutRelation 这个 BOOL 标识已经有约束关系。
- (void)setLayoutRelation:(NSLayoutRelation)layoutRelation {
    _layoutRelation = layoutRelation;
    self.hasLayoutRelation = YES;
}
  • 然后同样是重写了 self.secondViewAttributeset 方法,这里会根据不同的情况做不同的操作。
- (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.height.equalTo(@20.0f)

调用链如下:

//MASViewConstraint.m
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
    [self setLayoutConstantWithValue:secondViewAttribute];
}

//MASConstraint.m
- (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.m
- (void)setOffset:(CGFloat)offset {
    self.layoutConstant = offset;
}

//MASViewConstraint.m
- (void)setLayoutConstant:(CGFloat)layoutConstant {
    _layoutConstant = layoutConstant;

#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
    if (self.useAnimator) {
        [self.layoutConstraint.animator setConstant:layoutConstant];
    } else {
        self.layoutConstraint.constant = layoutConstant;
    }
#else
    self.layoutConstraint.constant = layoutConstant;
#endif
}

上面到最后会有个 CGFloat 类型的 layoutConstant 属性来保存值,并且在最后调用 install 方法的时候作为 constant 参数传入。

这里只看了下传入的 NSValueoffset 的情况,还有 centerOffsetsizeOffsetinsets,也都大同小异,就不熬述了。

其实这里有一点我没明白:
直到最后调用 install 方法前,self.layoutConstraint 这个 MASLayoutConstraint 类型的属性都是 nil,那么:

self.layoutConstraint.constant = layoutConstant;

这里的赋值又有什么意义呢?

第二种情况一般是直接传入一个视图:

make.height.equalTo(self)

这时,就会初始化一个 layoutAttribute 属性与 firstViewArribute (第一个约束参数对象)相同的 MASViewAttribute 对象,也就是第二个约束参数对象,上面代码意思就是使视图与 self 高度相等。

第三种情况会传入一个视图的 MASViewAttribute

make.height.equalTo(self.height)
//或者
make.height.equalTo(self.mas_height)

这两种写法其实效果是一样的,都是创建并返回一个 MASViewAttribute 对象。View+MASShorthandAdditions.h 这个 category 只有个 .h,定义了我们常用的属性和方法,但是具体实现还是调用的 View+MASAdditions 里面的方法,可以理解为去掉 mas_ 命名前缀。

这里还有许多属性可以设置,比如 multipliedBypriority等等,就不一一熬述了。

链式语法特性的重要一环

make.height.width.equalTo(@20);

这种同时设置多个约束属性的方式相信大家一定不陌生,认真看的人可能已经猜到了:那就是通过 delegate 的方式。

上面已经提到过,在 make.height 设置第一个约束属性时,

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute

方法中,会设置 MASViewConstraint 对象代理,其作用就是为了能够同时设置多个约束属性!我们来看看 make.height.width.width的调用链:

//MASConstraint.m

- (MASConstraint *)width {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}

//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];
}

//MASConstraintMaker.m
- (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;
    }
    ....
}

通过上面的调用链我们可以发现,最终就是通过 delegate 的方式,调用 MASConstraintMaker 工厂类中的 constraint:addConstraintWithLayoutAttribute: 方法,这也是链式语法能链起来的原因。

我们还可以发现因为 constraint 不为 nil,所以这次初始化并返回的不是 MASViewConstraint 对象,而是 MASCompositeConstraint 这个对象了,下面我们来看看这个类。

MASCompositeConstraint

我们先来回顾下开头是怎么介绍 MASCompositeConstraint 这个类的:“MASCompositeConstraint 是约束的集合,它里面有个私有的数组用来存放多个 MASViewAttribute 对象”。

我们接着上面的例子看:

make.height.width.equalTo(@20)

当走到 .width时:

- (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 判读里面,将 .height.wight 两条约束 MASViewConstraint 对象塞到数组里,创建 MASCompositeConstraint 对象,并且同样设置了 delegate,最后还把 self.constraints 里面事先添加好的约束 MASViewConstraint 对象替换成了 MASCompositeConstraint 对象。

- (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];
}

我们可以点击 MASCompositeConstraint 初始化方法里看看,它内部会通过 for 循环,把数组里面的所有 MASViewConstraint 对象同样设置了 delegate

- (id)initWithChildren:(NSArray *)children {
    self = [super init];
    if (!self) return nil;

    _childConstraints = [children mutableCopy];
    for (MASConstraint *constraint in _childConstraints) {
        constraint.delegate = self;
    }

    return self;
}

这么做的目的同时是为了能够继续链式调用,比如我们再加个 .left

make.height.width.left.equalTo(@20);

这时候的调用链如下:

//MASConstraint.m

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

//MASCompositeConstraint.m

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

- (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;
}

可以发现,这里又是通过 delegate 方式,调用 MASConstraintMaker 工厂类中的:

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    
    ....
    
    return newConstraint;
}

不过这次仅仅是初始化了个 MASViewConstraint 对象就直接返回了,然后回到上个方法中添加到 MASCompositeConstraint 的私有数组 self.childConstraints 中返回备用。

equalTo(@20)

因为到.left 时,返回的是 MASCompositeConstraint 对象,到这一步的时候会有点变化,调用链如下:

//MASConstraint.m

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

//MASCompositeConstraint.m

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attr, NSLayoutRelation relation) {
        for (MASConstraint *constraint in self.childConstraints.copy) {
            constraint.equalToWithRelation(attr, relation);
        }
        return self;
    };
}

可以发现,这里会循环之前准备好的私有数组 self.childConstraints,调用 MASViewConstraint.mequalToWithRelation 方法,和上面讲的一样了。

make.edges.equalTo(view)

我们再来看看这种写法,调用链如下:

//MASConstraintMaker.m

- (MASConstraint *)edges {
    return [self addConstraintWithAttributes:MASAttributeTop | MASAttributeLeft | MASAttributeRight | MASAttributeBottom];
}

- (MASConstraint *)addConstraintWithAttributes:(MASAttribute)attrs {
    __unused MASAttribute anyAttribute = (MASAttributeLeft | MASAttributeRight | MASAttributeTop | MASAttributeBottom | MASAttributeLeading
                                          | MASAttributeTrailing | MASAttributeWidth | MASAttributeHeight | MASAttributeCenterX
                                          | MASAttributeCenterY | 
                                          
                    ......
                        
    NSMutableArray *attributes = [NSMutableArray array];
    
    if (attrs & MASAttributeLeft) [attributes addObject:self.view.mas_left];
    if (attrs & MASAttributeRight) [attributes addObject:self.view.mas_right];
    if (attrs & MASAttributeTop) [attributes addObject:self.view.mas_top];
    
                    ......    
    
    NSMutableArray *children = [NSMutableArray arrayWithCapacity:attributes.count];
    
    for (MASViewAttribute *a in attributes) {
        [children addObject:[[MASViewConstraint alloc] initWithFirstViewAttribute:a]];
    }
    
    MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithChildren:children];
    constraint.delegate = self;
    [self.constraints addObject:constraint];
    return constraint;
}


代码太多省略了一部分,可以发现这段代码作用就是返回一个包含多条约束的 MASCompositeConstraint 对象,接着后面的操作也都是一样的了。

上面这种写法还可以这样:

make.edges.equalTo(UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f));

这里的 equalTo 需要注意下,它是一个,定义在 MASConstraint.h 中:

#define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...)    greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...)       lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))

#define mas_offset(...)                  valueOffset(MASBoxValue((__VA_ARGS__)))


#ifdef MAS_SHORTHAND_GLOBALS

#define equalTo(...)                     mas_equalTo(__VA_ARGS__)
#define greaterThanOrEqualTo(...)        mas_greaterThanOrEqualTo(__VA_ARGS__)
#define lessThanOrEqualTo(...)           mas_lessThanOrEqualTo(__VA_ARGS__)

#define offset(...)                      mas_offset(__VA_ARGS__)

我们来修改下代码:

make.edges.equalTo(MASBoxValue(UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f)));

可以发现,其实里面调用的是 MASBoxValue 这个宏,它将 CObjective-C 语言中的一些基本数据结构比如说 double CGPoint CGSize 这些值用 NSValue 进行包装。

这里还支持直接调用 sizecenter 等,具体实现都差不多,就不熬述了:

make.center.equalTo(CGPointMake(0, 50));
make.size.equalTo(CGSizeMake(200, 100));

make.height.equalTo(@[redView, blueView])

我再来看看这种传数组的,在走到 .equalTo 时,最终会调用 MASViewConstraint.m 里面的 equalToWithRelation 方法:

- (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 {   ....    }
    };
}

这边还是遍历数组,并且 MASViewConstraint 实现 NSCopying 协议,调用 [self copy] 会创建 MASViewConstraint 对象:

- (id)copyWithZone:(NSZone __unused *)zone {
    MASViewConstraint *constraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:self.firstViewAttribute];
    constraint.layoutConstant = self.layoutConstant;
    constraint.layoutRelation = self.layoutRelation;
    constraint.layoutPriority = self.layoutPriority;
    constraint.layoutMultiplier = self.layoutMultiplier;
    constraint.delegate = self.delegate;
    return constraint;
}

然后会根据传的数组里面的 Value 类型来做不同的操作,前面讲过就不熬述了:

- (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);
    }
}

最后便是生成 MASCompositeConstraint 对象,并通过 delegate 方式,调用 MASConstraintMaker 的方法,替换 self.constraints 数组里的约束:

- (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];
}

添加约束到视图

mas_makeConstraints 方法的最后会调用 [constraintMaker install] 方法来添加所有存储在 self.constraints 数组中的所有约束。

// MASConstraintMaker.m

- (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;
}

如果需要重新构建约束,也就是 调用 mas_remakeConstraints: 方法,会先取出视图的所有约束,然后通过一个 for 循环,调用 uninstall 来清空所有约束:

- (void)uninstall {
    if ([self supportsActiveProperty]) {
        self.layoutConstraint.active = NO;
        [self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
        return;
    }
    
    [self.installedView removeConstraint:self.layoutConstraint];
    self.layoutConstraint = nil;
    self.installedView = nil;
    
    [self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
}

如果不需要重新构建约束,会取出 self.constraints 数组中准备好的约束,通过 for 循环,调用 install 来把约束添加到视图上:

if (self.hasBeenInstalled) {
      return;
}

如果约束以及存在并是 active 会直接返回。

if ([self supportsActiveProperty] && self.layoutConstraint) {
     self.layoutConstraint.active = YES;
     [self.firstViewAttribute.view.mas_installedConstraints addObject:self];
     return;
}

如果 self.layoutConstraint 响应了 isActive 方法并且不为空,会激活这条约束并添加到 mas_installedConstraints 数组中,最后返回。

MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;

这边是获取即将用于初始化 NSLayoutConstraint 的子类 MASLayoutConstraint 的几个属性。

if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
     secondLayoutItem = self.firstViewAttribute.view.superview;
     secondLayoutAttribute = firstLayoutAttribute;
}

这边是判断当前即将添加的约束是否是 size 类型的并且 self.secondViewAttribute 也就是约束的第二个参数是 nil,(eg make.left.equalTo(@10))会自动将约束添加到约束的第一个参数视图的 superview 上。

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;

然后就会初始化 NSLayoutConstraint 的子类 MASLayoutConstraint

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;
}

这段代码会先判断是否有约束第二个参数的视图,有的话会寻找约束第一个和第二参数视图的公共 Superview,相当于求两个数的最小公倍数;如果不满足第一个条件,会判断约束第一个参数是否是 size 类型的,是的话直接取到它的视图;最后都不满足会直接取到约束第一个参数视图父视图。

MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
    existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
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];
}

如果需要升级当前的约束就会获取原有的约束,并替换为新的约束,这样就不需要再次为 view 安装约束。如果原来的 view 中不存在可以升级的约束,那么就会在上一步寻找到的 installedView 上面添加约束。

结束语

阅读懂源码真是一件很爽的事情,如果有什么理解的不到位的地方大家多多指正。也希望大家能够耐心的看下去,一定会有所收获的。

参考链接

http://www.cnblogs.com/ludashi/p/5591572.html

https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/Masonry/iOS%20%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90%20---%20Masonry.md

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容