顺蔓摸瓜:解析Masonry源码

1. Masonry调用方式入门

#import "Masonry.h"
[self.containerView addSubview:self.newsView];
[self.newsView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.right.equalTo(self.containerView);
    make.height.equalTo(@(100));
}];
[self.containerView addSubview:self.bannerView];
[self.bannerView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.right.height.equalTo(self.newsView);
    make.top.equalTo(self.newsView.mas_bottom);
}];

2. Masonry调用解析

2.1 查看 mas_makeConstraints:方法的实现

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    //关闭 Autoresize 布局
    self.translatesAutoresizingMaskIntoConstraints = NO;
    //创建 MASConstraintMaker 管理类
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    //保存外部传入的约束集合
    block(constraintMaker);
    //安装约束
    return [constraintMaker install];
}
  1. 首先帮助我们关闭 Autoresize 布局(只能相对于父视图改变上左下右宽高的大小)
  2. 然后创造一个管理器 MASConstraintMaker,和当前视图 UIView 建立 weak 链接
  3. 执行传给用户的block,同时将管理器传给用户
  4. 安装用户调用管理器创建的对象
  • 可见,此处运用了外观模式,开发者只需要调用管理器的方法,而不需要了解内部情况。

2.2 通过 make.left 方法分析 MASConstraintMaker 类

    make.left.right.height.equalTo(self.newsView);
//step1
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

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

//step3
- (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]) {
        //step4
        //replace with composite constraint  instead of MASViewConstraint 
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    if (!constraint) {
        //step5
        // MASCompositeConstraint 类调用
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
      //step6
    // 创建对象
    return newConstraint;
}
  • 由于 constraint:addConstraintWithLayoutAttribute方法传进来的参数是 nil ,因此只能执行代码中的 step6 return newConstraint;,将当前←的属性保存到当前 MASConstraint 对象中。

2.3 通过 make.left.right 方法分析 MASViewConstraint 类

    make.left.right.height.equalTo(self.newsView);
  • 在 MASViewConstraint 类种未找到 right 方法,发现它继承于父类 MASConstraint
@interface MASViewConstraint : MASConstraint <NSCopying>
  • MASConstraint类
- (MASConstraint *)right {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
    //宏定义,如果子类未实现当前方法,就抛出异常
    MASMethodNotImplemented();
}

父类 MASConstraint 中未实现 right 方法的底层,需要返回 MASViewConstraint 中查找 addConstraintWithLayoutAttribute: 方法

  • MASViewConstraint类如下:
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
    //调用代理 MASConstraintMaker 管理类中的方法
    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}

此处为了将当前链式结构的 leftright 等方法都保存到一个对象中,保证 leftright 等方法返回的对象是 MASConstraint,同时由同一个管理类来处理,是一个很好的架构设计。

不仅 MASConstraintMaker 对象中含有 leftright 等方法,MASConstraint 对象中也含有leftright 等方法。

再回到 MASConstraintMaker 对象中,看以下语句的执行过程

  • MASViewConstraint
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
  • MASConstraintMaker
//step3
- (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]) {
        //step4
        //replace with composite constraint  instead of MASViewConstraint 
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    if (!constraint) {
        //step5
        // MASCompositeConstraint 类调用
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
      //step6
    // 创建对象
    return newConstraint;
}

由于传进去了 MASViewConstraint 对象,会执行 step4,执行了以下操作:

  1. 将刚刚传入的 leftright装进数组
  2. 创建 MASCompositeConstraint 对象
  3. MASCompositeConstraint 对象的代理设置给 MASConstraintMaker对象,便于继续执行暴露给开发者 的 width 等方法
  4. 用新创建的 MASCompositeConstraint 代替数组中的 MASViewConstraint 对象
  5. 返回 MASCompositeConstraint 对象给 right 方法
  • MASConstraintMaker
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
    //获取当前 MASViewConstraint 所在的 index
    NSUInteger index = [self.constraints indexOfObject:constraint];
    NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
    //用 MASCompositeConstraint 代替同一链条上的的 MASViewConstraint
    [self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}

由于此处通过调用 MASViewConstraint 的 right 方法,来创建 MASCompositeConstraint 对象,因此需要保留最新的属性数组,方便给每条链条设置数值并安装约束。

2.4 通过 make.left.right.height 方法分析 MASCompositeConstraint 类

    make.left.right.height.equalTo(self.newsView);

在 MASViewConstraint 类中也未找到 height 方法,发现它继承于父类 MASConstraint

@interface MASCompositeConstraint : MASConstraint
  • MASConstraint类
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
    MASMethodNotImplemented();
}

父类 MASConstraint 中也未实现 height 方法的底层,需要返回 MASCompositeConstraint 中查找 addConstraintWithLayoutAttribute: 方法

  • MASCompositeConstraint类如下:
- (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;
}

操作如下:

  1. 获取约束对象
  2. 并将最新的约束对象保存到当前链条的约束数组中(已经有3条)
  3. 返回普通的 MASViewConstraint对象

再回到 MASConstraintMaker 对象中,查看获取 MASConstraint 约束的过程

  • MASCompositeConstraint
    MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
  • MASConstraintMaker
//step3
- (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]) {
        //step4
        //replace with composite constraint  instead of MASViewConstraint 
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    if (!constraint) {
        //step5
        // MASCompositeConstraint 类调用
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
      //step6
    // 创建对象
    return newConstraint;
}

直接执行了 step6 ,创建了约束对象,就立即返回给了 MASCompositeConstraint 对象,保存在 MASCompositeConstraint 对象内部的约束数组中。

2.5 通过 make.left.right.height.equalTo(self.newsView) 方法来分析 MASCompositeConstraint 类

在 MASViewConstraint 类中也未找到 equalTo 方法,发现它继承于父类 MASConstraint

@interface MASCompositeConstraint : MASConstraint
  • MASConstraint类
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

父类 MASConstraint 中也未实现 equalToWithRelation 方法的底层,需要返回 MASCompositeConstraint 中查找该方法

  • MASCompositeConstraint类如下:
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attr, NSLayoutRelation relation) {
        for (MASConstraint *constraint in self.childConstraints.copy) {
            constraint.equalToWithRelation(attr, relation);
        }
        return self;
    };
}

在调用equal方法后,会遍历约束数组中的所有子约束对象,调用其equalToWithRelation方法,接下来,我们进入 MASViewConstraint 查看此方法

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

由于传入的不是数组,只会走 step8,对约束设置和第二个约束对象的关系,如视图 A 的高度相对于它的父视图的高度之间的关系。

更新第二个约束对象信息的代码如下:

  • MASViewConstraint
- (void)setSecondViewAttribute:(id)secondViewAttribute {
    if ([secondViewAttribute isKindOfClass:NSValue.class]) {
        //更新约束对象,同时设置相对值
        [self setLayoutConstantWithValue:secondViewAttribute];
    } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
        //使用原对象的约束属性,如make.left.mas_equalTo(self)
        //参考的是第二个对象的left
        _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);
    }
}
  • MASConstraint
- (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);
    }
}

2.6 查看 install方法的实现

  • MASConstraintMaker
- (NSArray *)install {
    if (self.removeExisting) {
        //remake移除重复约束
        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;
}
  • MASCompositeConstraint
- (void)install {
    for (MASConstraint *constraint in self.childConstraints) {
        constraint.updateExisting = self.updateExisting;
        [constraint 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;
    }
    
    //step11
    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) {
        // 默认为原约束对象的父视图superview及其约束属性如left
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }
    
    //step12
    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;
    
    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) {
        //update installedView for width or height
        self.installedView = self.firstViewAttribute.view;
    } else {
        //update installedView as superview
        self.installedView = self.firstViewAttribute.view.superview;
    }


    MASLayoutConstraint *existingConstraint = nil;
    if (self.updateExisting) {
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    }
    if (existingConstraint) {
        // just update the constant
        existingConstraint.constant = layoutConstraint.constant;
        //update layout constraint
        self.layoutConstraint = existingConstraint;
    } else {
        [self.installedView addConstraint:layoutConstraint];
        self.layoutConstraint = layoutConstraint;
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}

使用面向对象的方式代替了系统添加约束的方式

UIView *superview = self.view;

UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[superview addConstraints:@[

    //view1 constraints
    [NSLayoutConstraint constraintWithItem:view1
                                 attribute:NSLayoutAttributeTop
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:superview
                                 attribute:NSLayoutAttributeTop
                                multiplier:1.0
                                  constant:padding.top],

    [NSLayoutConstraint constraintWithItem:view1
                                 attribute:NSLayoutAttributeLeft
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:superview
                                 attribute:NSLayoutAttributeLeft
                                multiplier:1.0
                                  constant:padding.left],

    [NSLayoutConstraint constraintWithItem:view1
                                 attribute:NSLayoutAttributeBottom
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:superview
                                 attribute:NSLayoutAttributeBottom
                                multiplier:1.0
                                  constant:-padding.bottom],

    [NSLayoutConstraint constraintWithItem:view1
                                 attribute:NSLayoutAttributeRight
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:superview
                                 attribute:NSLayoutAttributeRight
                                multiplier:1
                                  constant:-padding.right],

 ]];

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

推荐阅读更多精彩内容