iOS开发Masonry框架源码解析

iOS开发Masonry框架源码解析

前言:这个框架编程思想主要包括链式编程

这是一个iOS在控件布局中的轻量级框架,简化了NSLayoutConstraint的使用方式,让我们用链式变成的思想进行对View控件的约束。

本篇主要围绕Masonry框架源码进行解析,从而透析Masonry是如何对NSLayoutConstraint进行封装的。

####1.OC中的链式编程是如何实现的?

链式编程的好处:

点语法+事物+串联

1.简化代码

2.代码可读性高

调用:

self.where.select;

~~~

- (ViewController*)where{

return self;

}

- (void)select{

}

~~~

用getter+有参数的时候应该如何写呢?

~~~

- (ViewController*)where{

return self;

}

//block有保存代码块的特性

- (void(^)(NSString*))select{

void(^block)(NSString *word) = ^(NSString *str){

};

}

~~~

举个栗子:self.where.select(@"参数")。

#####2.框架解析正文

Masonry框架和NSLayoutConstraint调用的对比:

###实际应用:

![avatar](images/Masonry类结构图.png)

#####1.对谁做约束

View+MASAdditions 就是 Masonry 的一个外部的入口,实质上就是 UIView 的一个 Category 作用就是用来设置 MASViewAttribute 的属性,并实例化,并且指定当前的 UIView 对应的约束(LayoutAttribute)

MASUtilities.h 主要是这里除了平台相关代码外,还有些宏的定义和静态方法。Masonry 也对 iOS 和 macOS 做了兼容,在 macOS 里就是 NSView。

其中静态方法

```

static inline id _MASBoxValue(const char *type, ...)

```

是我们经常使用的 mas_equalTo 这个方法,这里可以看到它是如何支持变参和如何将 float,double,int 这样的值类型数据转换成和 equalTo 一样的对象 NSNumber 数据的。这个写法灵感来自GitHub [- specta/expecta: A Matcher Framework for Objective-C/Cocoa ](https://github.com/specta/expecta)。 mas_equalTo 和 equalTo 都是宏定义的。

```

#define mas_equalTo(...)                equalTo(MASBoxValue((__VA_ARGS__)))

#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))

#define equalTo(...)                    mas_equalTo(__VA_ARGS__)

```

方法实现:

```

- (MASConstraint * (^)(id))equalTo {

    return ^id(id attribute) {

        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);

    };

}

- (MASConstraint * (^)(id))mas_equalTo {

    return ^id(id attribute) {

        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);

    };

}

```

宏定义不同但是实现方法相同这样写就是避免宏定义冲突的一种方式。

mas_makeConstraints 的的 block 参数会将创建的 MASConstraintMaker 这个工厂类对象暴露出去,让我们去设置这个类对象中的 MASConstraint 属性,然后通过该对象的 install 方法将当前视图中所有添加的约束添加到一个数组里。该数组里存储是 MASViewConstraint 对象,对应的就是 NSLayoutConstraint。

```

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;

```

```

//创建约束

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {

    //去掉自动autoresizing转为约束的

    self.translatesAutoresizingMaskIntoConstraints = NO;

    //构建builder

    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];

    //运行builder

    block(constraintMaker);

    //附值约束返回

    return [constraintMaker install];

}

```

#####2.如何做约束

Masonry通过MASConstraintMaker来创建MASConstraint对象。里面有个 constraints 数组专门用来存储创建的这些对象。前面 mas_makeConstraints 的那个 Block 暴露出的就是 MASConstraintMaker 对象。

```

//property 重写了getter方法,使得每次链式调用相当于构造一个约束

@property (nonatomic, strong, readonly) MASConstraint *left;

```

```

//重写getter方法,调用.方法相当于构造约束

- (MASConstraint *)left {

    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];

}

```

```

//通过NSLayoutAttribute添加约束

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

    //构造view的MASViewAttribute

    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];

    //通过MASViewAttribute构造第一个MASViewConstraint

    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];

    //如果存在contraint,则把constraint和newConstraint组合成MASCompositeConstraint

    if ([constraint isKindOfClass:MASViewConstraint.class]) {

        //replace with composite constraint

        NSArray *children = @[constraint, newConstraint];

        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];

        compositeConstraint.delegate = self;

        //替换原来的constraint成新的MASCompositeConstraint

        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];

        return compositeConstraint;

    }

    //不存在则设置constraint到self.constraints

    if (!constraint) {

        newConstraint.delegate = self;

        [self.constraints addObject:newConstraint];

    }

    return newConstraint;

}

```

这里会发现每次 getter 都会创建一个新的 MASViewConstraint 对象,这里通过将新的 MASViewConstraint 对象的 delegate 设置成自己的方式让新对象也能够调用相同的方法创建一个新的 MASViewConstraint 对象,使得能够支持进行链式的调用。

#####约束收集完成之后完成之后的处理

MASViewConstraint这个类是对 NSLayoutConstriant 的封装。它的父类是 MASConstraint,MASConstraint 是一个抽象不可实例的类,里面有接口和协议。它的兄弟类是 MASCompositeConstraint,里面有个数组专门存储 MASViewConstraint 对象。

```

//附值约束返回

    return [constraintMaker 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) {

        //设置更新key

        constraint.updateExisting = self.updateExisting;

        [constraint install];

    }

    //去除所有缓存的约束结构体

    [self.constraints removeAllObjects];

    return constraints;

}

```

```

- (void)install {

    //已经有约束

    if (self.hasBeenInstalled) {

        return;

    }


    //支持active且已经有了约束

    if ([self supportsActiveProperty] && self.layoutConstraint) {

        //激活约束

        self.layoutConstraint.active = YES;

        //添加约束缓存

        [self.firstViewAttribute.view.mas_installedConstraints addObject:self];

        return;

    }


    //获得item1,attribute1,item2,attribute2

    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)

    //如果attribute是sizeattribute并且没有第二个attribute

    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {

        //默认设置item2为superview

        secondLayoutItem = self.firstViewAttribute.view.superview;

        secondLayoutAttribute = firstLayoutAttribute;

    }


    //生成约束

    MASLayoutConstraint *layoutConstraint

        = [MASLayoutConstraint constraintWithItem:firstLayoutItem

                                        attribute:firstLayoutAttribute

                                        relatedBy:self.layoutRelation

                                          toItem:secondLayoutItem

                                        attribute:secondLayoutAttribute

                                      multiplier:self.layoutMultiplier

                                        constant:self.layoutConstant];


    //设置priority和mas_key

    layoutConstraint.priority = self.layoutPriority;

    layoutConstraint.mas_key = self.mas_key;


    //如果第二个attribute有view对象

    if (self.secondViewAttribute.view) {

        //则获取两个view的最小公共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);

        //设置约束view为此view

        self.installedView = closestCommonSuperview;

    } else if (self.firstViewAttribute.isSizeAttribute) {

        //如果是size attribute则为本身

        self.installedView = self.firstViewAttribute.view;

    } else {

        //其它则是superview

        self.installedView = self.firstViewAttribute.view.superview;

    }

    //已经存在的约束

    MASLayoutConstraint *existingConstraint = nil;

    //需要更新

    if (self.updateExisting) {

        //则获得此生成的约束,返回和installedview的约束是同类的约束

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

    }

}

```

总结:

###参考资料

1.[Apple-NSLayoutConstraint](https://developer.apple.com/reference/uikit/nslayoutconstraint)

2.[Auto Layout Guide](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/index.html)

3.[Visual Format Language](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html)

3.[深入剖析Auto Layout,分析iOS各版本新增特性](http://www.starming.com/index.php?v=index&view=84&utm_source=tuicool&utm_medium=referral)

4.[iOS开发之Masonry框架源码深度解析](http://www.cnblogs.com/ludashi/p/5591572.html)

5.[Masonry源代码分析](http://blog.csdn.net/colorapp/article/details/45030163)

6.[史上比较用心的纯代码实现 AutoLayout](http://ios.jobbole.com/85829/)

7.[iOS开发之Masonry框架源码解析](https://www.cnblogs.com/ludashi/p/5591572.html)

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

推荐阅读更多精彩内容