Masonry原理与链式编程

在iOS开发中,Masonry是我们常用的一个轻量级的布局框架,它以链式语法的形式优雅的实现了自动布局。
下面我们以一个典型的布局代码为入口来剖析Masonry

[self.redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(100.f, 100.f));
        make.left.top.offset(@20.f);
}];

通过 mas_makeConstraints 把我们添加的约束写在其提供的block内,非常方便的设置约束关系。可以想象,万变不离其宗,Masonry一定是封装了系统的NSLayoutConstraint。

一、Masonry中的那些类

1、Masonry类关系图

masonry类结构图.png

2、核心类的说明

  1. View+MASAdditions:是方便我们对UIView添加约束所做的一个UIView的分类,里面包括了我们最常用的 mas_makeConstraints、mas_updateConstraints、mas_remakeConstraints 3个方法和一些在添加约束过程中用到的属性。
  2. MASConstraint:Masony中约束的基类,封装了Masonry约束的通用属性和方法。Masonry不直接使用此类,而是使用它的子类 MSAViewConstraint和MASCompositeConstraint。
  3. MSAViewConstraint:对NSLayoutConstriant的封装,负责最终产生NSLayoutConstriant对象,并将该对象加载到目标view。
  4. MASCompositeConstraint:对MSAViewConstraint
    的组合,以数组的形式来维护一组约束。当使用诸如 make.left.right 会产生一个约束组合以 MASCompositeConstraint 的形式来管理。
  5. MASConstraintMaker:一个产生约束的工厂类,也就是我们在block中所使用的make,核心工作是产生约束和装载约束。
  6. MASViewAttribute:对产生 NSLayoutConstraint 所需元素的封装,包括view、layoutAttribute等关键元素。
  7. MASLayoutConstraint :继承于 NSLayoutConstraint ,只是增加了一个标识约束的mas_key,用于方便区分约束。

二、核心源码解析

我们从入口方法 mas_makeConstraints 入手,来看看Masonry是如何把约束加载到目标view上的
1、mas_makeConstraints

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    // 关闭自动添加约束,使手动添加的约束生效
    self.translatesAutoresizingMaskIntoConstraints = NO;
    // 创建约束工厂
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    // 通过block回调,用工厂去创建并记录约束
    block(constraintMaker);
    // 安装约束并返回约束数组
    return [constraintMaker install];
}

该方法首先生成一个maker工厂对象,接着把该对象作为参数传递到我们设置约束的block中,也就是说用这个工厂对象来制造约束,并在最后用install方法来装载所有的约束。
2、make.left 约束的产生

  1. 在 MASConstraintMaker 中找到left的getter方法
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
  1. 调用方法 addConstraintWithLayoutAttribute,
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
  1. 接着进入 constraint: addConstraintWithLayoutAttribute: ,注意此时第一个参数constraint为nil,
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    // 创建MASViewAttribute
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    // 根据 viewAttribute 创建 MASViewConstraint
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    // 当链式语法第一次创建约束时候(make.left),contraint为nil
    // 第二次(make.left.right),constraint存在,那么组合成MASCompositeConstraint对象
    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;
}

这是一个生产约束的核心方法:
到这里我们的make.left就成功产生了一个约束对象,并将该约束对象作为函数的返回值return。
可以看到当 constraint 为 nil 时,产生的约束(MASViewConstraint)被直接添加进 constraints 数组中;
当 constraint 存在时,约束被组合成 MASCompositeConstraint 。
并且以上两种情况约束的代理都被设置成了self(MASConstraintMaker),那我们进入 MASViewConstraint 约束对象中来看看设置这个代理究竟有什么用,

  1. 进入约束对象 MASViewConstraint, 查看 make.left.right 是如何产生第二个约束right的
    从上面可知调用make.left会得到一个 MASViewConstraint 对象,那么make.left.right 产生第二个约束也就是要从第一个约束入手,找到 MASViewConstraint 的基类方法
- (MASConstraint *)right {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}

然后进入 addConstraintWithLayoutAttribute

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

    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}

从这里我们看到了在产生约束时设置的 delegate ,也就是 MASConstraintMaker 工厂对象,到这里就不用再多赘述了,简单来说就是 maker 产生一个新约束(right)和第一个约束(left)组合成了一个 MASCompositeConstraint 组合约束对象。

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

因为内部较繁琐,但不复杂,所以在此就不展开来剖析。总结来讲就是对 MASViewConstraint 或者 MASCompositeConstraint 对象设置 relation 关系(equal)和具体的约束值(@50)。

  1. nstall方法 装载约束
    maker的install方法
 // MASConstraintMaker
- (NSArray *)install {
    // 是否移除原有约束
    if (self.removeExisting) {
        // 获取当前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;
}

由maker的install方法调用具体约束的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;
    }
    
    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) {
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }
    // 产生 MASLayoutConstraint (NSLayoutConstraint)
    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;
    
    // 查找装载约束的View
    // 如果 secondView 存在那么递归查找最近的公共父视图
    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;
    }


    MASLayoutConstraint *existingConstraint = nil;
    // 是否需要更新约束,如果需要那么找出需要被更新的约束
    if (self.updateExisting) {
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    }
    // 装载约束到目标View
    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];
    }
}

最后调用 install 装载约束,到此为止约束就被添加到view上了。

三、链式语法浅析

  1. MASConstraintMaker 的left方法为例
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

left 方法返回的是一个约束对象,由这个约束对象又可以继续“点”产生第二个约束,这样就实现了连续点。

  1. MASConstraint 的offset方法
- (MASConstraint * (^)(CGFloat))offset {
    // 匿名block,以CGFloat为参数,并返回MASConstraint来支持链式调用
    return ^id(CGFloat offset){
        self.offset = offset;
        return self;
    };
}

综上可知,能连续点的条件是“点语法”返回的对象能继续使用“点语法”。

四、结束语

以上,通过一个Masonry布局语句,追根溯源,简要概括了约束的产生和装载的整个过程,并通过Masonry对点语法有了了解。
世界和平万岁!

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

推荐阅读更多精彩内容

  • Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使...
    爱敲代码的果果阅读 1,856评论 0 2
  • Masonry是iOS在控件布局中经常使用的一个轻量级框架。Masonry让NSLayoutConstraint使...
    AngeloD阅读 922评论 0 3
  • Masonry是一个轻量级的布局框架,拥有自己的描述语法,采用更优雅的链式语法封装自动布局,简洁明了并具有高可读性...
    3dcc6cf93bb5阅读 1,740评论 0 1
  • (一)Masonry介绍 Masonry是一个轻量级的布局框架 拥有自己的描述语法 采用更优雅的链式语法封装自动布...
    木易林1阅读 2,297评论 0 3
  • 1.《成为作家》 简介: 什么人能成为作家?写作需要天才吗?作家是可以教会的吗?文学创作需要什么天赋、才能和技艺?...
    为楠橘白阅读 11,028评论 1 4