纵有疾风起
人生不言弃

Masonry 源码解析

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

结构概览

Masonry 源码解析插图
  • 最上面的几个 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.mif ([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

文章转载于:https://www.jianshu.com/p/3fce11e370b1

原著是一个有趣的人,若有侵权,请通知删除

未经允许不得转载:起风网 » Masonry 源码解析
分享到: 生成海报

评论 抢沙发

评论前必须登录!

立即登录