理解Masnory—Part1

Masnory做了什么?

简单说,只是简化了代码实现Autolayout的方式,本质还是系统API实现的(后面会讲到)。

创建一个view,左边距离self.view左边50。简单对比一下:

  • 系统API:
 NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view
                                                                attribute:NSLayoutAttributeLeft
                                                                relatedBy:NSLayoutRelationEqual
                                                                   toItem:self.view
                                                                attribute:NSLayoutAttributeLeft
                                                               multiplier:1
                                                                 constant:50];
 [self.view addConstraint:constraint];
  • Masnory:
[view mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view).offset(50);
    }];

简单看来,系统API更繁琐,Masnory更简洁,当创建很多约束时这个特点就尤为突出,那么Masnory是怎么做到既简洁又好用的呢?

Masnory怎么做到的?

首先得说说里面几个关键类:

  • MASViewConstraint
                                                        / (id) item (or (UIView *) view)
                  (MASViewAttribute *) firstViewAttribute
                 /                                      \(NSLayoutAttribute) layoutAttribute
                  (MASViewAttribute *) secondViewAttribute ...
                 /
MASViewConstraint 
                 \
                  (NSLayoutRelation) layoutRelation
                 \
                  (CGFloat) layoutMultiplier
                 \
                  (CGFloat) layoutConstant
                 ...

这个类它是Masnory的核心,为什么这样说?看看它的其中一个方法-install就知道,其中有一段实现特别关键:

MASLayoutConstraint *layoutConstraint = [MASLayoutConstraint constraintWithItem:self.firstViewAttribute.item
                                                                      attribute:self.firstViewAttribute.layoutAttribute
                                                                      relatedBy:self.layoutRelation
                                                                         toItem:self.secondViewAttribute.item
                                                                      attribute:self.secondViewAttribute.layoutAttribute
                                                                     multiplier:self.layoutMultiplier
                                                                       constant:self.layoutConstant];

是不是和NSLayoutConstraint的创建很相似,因为MASLayoutConstraint本就是继承自NSLayoutConstraint,此处的self是指MASViewConstraint。你猜对了,Masnory在用MASViewConstraint的属性值创建一个NSLayoutConstraint对象,里面的所有类都会围绕MASViewConstraint做文章(后面会一一分析),所以也印证了开头总结的,Masnory本质还是使用了系统的API在做约束。

  • MASConstraintMaker
    这是服务MASViewConstraint很重要的一个类,更准确说是服务MASConstraint,因为MASViewConstraint继承自MASConstraint。它是MASConstraint的工厂类,里面有一个工厂方法会频繁用到,先提在这儿,后面也会重点介绍。
 - (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 (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}
  • MASCompositeConstraint
    这也是继承自MASConstraint,它有个很重要的功能,单次执行超过1个约束以上,就会用到它,它有一个数组childConstraints专门保存每个约束对应的MASViewConstraint对象。(PS:什么叫单次执行,这是我的理解,例如:make.left.top.equalTo(self.view).offset(60); 这个单次执行就会有两个约束left、top,这时就会产生一个MASCompositeConstraint对象。)

几个重点类和概念心里先有个谱,现在才正式跟踪代码,看具体怎么实现。

Part1 只围绕一个简单的写法来跟代码,先走通一个方式,后续再添加其他情况。

例子很简单,在self.view上添加imageView 距离上下左右都是60

    UIImageView *imageView = [[UIImageView alloc]init];
    [self.view addSubview:imageView];
    
    @WeakObj(self);
    [imageView mas_makeConstraints:^(MASConstraintMaker *make) {
        @StrongObj(self);
        make.left.top.equalTo(self.view).offset(60);
        make.right.bottom.equalTo(self.view).offset(-60);
    }];

1、链式语法:
如果你对它很熟悉,可以略过。
链式语法我是这样理解的:
我把里面的涉及到方法,left、top、equalTo、offset都看作同一个类型的属性,然后用get方法串起来的,说起来有点抽象,不过还原一下就知道了。这里拿equalTo举例:

@property(copy, nonatomic) MASConstraint *(^equalTo)(id);
- (MASConstraint *(^)(id))equalTo{

}

2、分析工厂方法
首先会进入到mas_makeConstraints方法,这是view的分类方法,所以imageView才能访问这个方法。这个方法最主要的功能是创建了一个maker传递给block

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    //关闭自动布局
    self.translatesAutoresizingMaskIntoConstraints = NO;
    //创建maker
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    //添加约束
    return [constraintMaker install];
}

block依次执行left、top、equalTo、offset,这里我看做有两个约束,left、top

第一个约束总是maker去执行,而且每个约束最终都会走到MASConstraintMaker的一个方法,一个创建MASConstraint子类的工厂方法FUNC1,重点分析(后面都叫FUNC1):

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    //利用枚举值NSLayoutAttribute、self.view创建的MASViewAttribute
    //其实也只是作为MASViewAttribute的两个属性值保存起来而已
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    
    //利用viewAttribute创建MASViewConstraint,其中只保存了FirstViewAttribute的值。
    //FirstViewAttribute就是需要添加约束的一方的所有属性,SecondViewAttribute会在equalTo后得到
    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 (!constraint) {
        newConstraint.delegate = self;
        // constraints 存放的是单次执行的结果
       /*
        * make.left.top.equalTo(self.view).offset(60);
        * make.right.bottom.equalTo(self.view).offset(-60);
        * 就是执行个数为两个,每次结果都是 MASCompositeConstraint
        *
        */
        
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

里面的判断很有意思,会得到前面提到的,单个执行约束超过1个以上就会得到一个MASCompositeConstraint对象,只有一个约束就是MASViewConstraint对象。

make.left,入参constraint = nil,返回值是MASViewConstraint

make.left.top,入参constraint = make.left = MASViewConstraint,返回值就是MASCompositeConstraintMASCompositeConstraint.childConstraints = @[MASViewConstraint0 (left) ,MASViewConstraint1 (top)](每个约束对象都是MASViewConstraint),具体实现如下:

// MASConstraint.h
// 此处 self = make.left = MASViewConstraint
- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}

// MASViewConstraint.h
// 工厂方法FUNC1判断里面,maker作为了MASViewConstraint的代理,也作为了MASCompositeConstraint的代理
// 此处 self.delegate = MASConstraintMaker
// 所以又回到工厂方法FUNC1了,这也是链式语法串起来的意义
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");

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

大胆假设make.left.top.right ,入参constraint = make.left.top = MASCompositeConstraint, MASCompositeConstraint.childConstraints = @[MASViewConstraint0 (left) ,MASViewConstraint1 (top) ,MASViewConstraint2 (right)],返回值是MASViewConstraint具体实现如下:

// MASConstraint.h
// self = make.left.top = MASCompositeConstraint
- (MASConstraint *)right {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}

// MASCompositeConstraint.h
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    [self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
  //返回自身
    return self;
}
- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    // 还不明白为什么这样做,可能是怕释放掉
    id<MASConstraintDelegate> strongDelegate = self.delegate;
    // FUNC1 能找到maker作为了MASCompositeConstraint的代理   
    // self.delegate = MASConstraintMaker ,也回到了FUNC1
    MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
    // 此处代理是为后面的secondAttribute设置用的
    newConstraint.delegate = self;
    // 添加约束到数组,例子中是right的约束
    [self.childConstraints addObject:newConstraint];
    // 返回值是MASViewConstraint
    return newConstraint;
}

四个约束可以自己尝试下。

3 、添加约束

equalTo就比较简单,主要是添加secondViewAttribute、layoutRelation。
offset也比较简单,是添加layoutConstant

所有约束的属性值都有了,就需要添加到对应的firstViewAttribute.view上去了,主要是调用到install

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

可以看到install的时候,会去遍历数组constraints,而constraints里面存放的是单次执行的MASConstraint对象,前面有说到,单次执行是1个约束的时候得到的是MASViewConstraint,单次执行1个约束以上的时候得到是MASCompositeConstraint

其实最终都会走向MASViewConstraint的install方法。这个方法就是先拼装出一个NSLayoutConstraint对象,然后添加到对应的View

自此,简单的Masnory使用就分析完了,后面还会补充其他部分。

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

推荐阅读更多精彩内容

  • iOS_autoLayout_Masonry 概述 Masonry是一个轻量级的布局框架与更好的包装AutoLay...
    指尖的跳动阅读 1,142评论 1 4
  • 一、前言 关于苹果的布局一直是我比较纠结的问题,是写代码来控制布局,还是使用storyboard来控制布局呢?以前...
    iplaycodex阅读 2,397评论 0 1
  • (一)Masonry介绍 Masonry是一个轻量级的布局框架 拥有自己的描述语法 采用更优雅的链式语法封装自动布...
    木易林1阅读 2,302评论 0 3
  • Masonry是一个轻量级的布局框架,拥有自己的描述语法,采用更优雅的链式语法封装自动布局,简洁明了并具有高可读性...
    3dcc6cf93bb5阅读 1,740评论 0 1
  • 我们先来看看是如何开始使用Masonry的,一般我们使用这个布局框架的时候,都会调用以下代码。。。。。 [self...
    smile小芳阅读 1,141评论 0 0