『转』追求Masonry

本文内容全部转载自追求Masonry

目录

『使用』

一、MASConstraintMaker
二、MASConstraint
三、UIView(12月7日新增)
四、Relationship
五、multiplier
六、Constant
七、小技巧
1、如果等式2边的Attribute是一样的,我们可以省略等式右边的Attribute
2、如果是等于关系,并且右边的view是父View。连equalTo也可以省略
3、如果equalTo里面传的是NSValue类型,效果跟设置offset是一样的
4、如果offset为0,其实也是可以省略的...
八、设置或更新约束
九、批量设置约束
十、Priority
十一、key
十二、Shorthand(12月7日新增)

『拨开Masonry的衣服』

一、结构
二、Help
三、Shorthand
四、Public
五、Core
六、Property

『实现』

一、执行"make.left"
二、执行".top"
三、执行".equalTo(view1.superview)"
四、执行".offset(10)"
五、mas_makeConstraints
1、将self.translatesAutoresizingMaskIntoConstraints至为NO。translatesAutoresizingMaskIntoConstraints表示是否将设置的Frame转化为约束。当自己设置约束的时候需要将其置为NO
2、创建出MASConstraintMaker对象
3、通过block抛出到外面设值
4、constraintMaker install
六、constraintMaker install
七、constraint install
1、如果已经installed就不做任何操作
2、从ViewAttribute中剥离出item和attribute。前面我们介绍过MASViewAttribute的类主要是将item和attribute2个属性封装在了一起。
3、secondViewAttribute的值来自于.equalTo(item.attribute)中的item.attribute。当我们写下类似make.left.offset(10);约束的时候,是没有secondViewAttribute的,这时候默认secondItem为其父View,secontAttribute等于firstLayoutAttribute。这就解释了为什么可以这样写make.left.offset(10);
4、创建真正用于Autolayout的约束layoutConstraint
5、将priority和key赋值
6、找到要添加约束的installView。如果是2个View之间的约束,需要寻找这2个View最接近的共同父View。添加约束
7、添加或更新约束。当调用mas_updateConstraints的时候updateExisting=YES。这时候会查找是否有已经存在的约束。有就更新,没有就添加。如果是mas_makeConstraints或mas_remakeConstraints,则直接添加

『Extension』

『Reference』


『使用』

用Masonry创建一个完整的约束应该是这样的

//view1的左边距离父View左边10个点:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.equalTo(view1.superview.mas_left).multipliedBy(1).offset(10);
}];  

一、MASConstraintMaker

makeMASConstraintMaker类型。MASConstraintMaker给我们提供了22种Attribute类型

//Basic Attribute
@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
@property (nonatomic, strong, readonly) MASConstraint *leading;
@property (nonatomic, strong, readonly) MASConstraint *trailing;
@property (nonatomic, strong, readonly) MASConstraint *width;
@property (nonatomic, strong, readonly) MASConstraint *height;
@property (nonatomic, strong, readonly) MASConstraint *centerX;
@property (nonatomic, strong, readonly) MASConstraint *centerY;
@property (nonatomic, strong, readonly) MASConstraint *baseline;

//Margin Attribute
@property (nonatomic, strong, readonly) MASConstraint *leftMargin;
@property (nonatomic, strong, readonly) MASConstraint *rightMargin;
@property (nonatomic, strong, readonly) MASConstraint *topMargin;
@property (nonatomic, strong, readonly) MASConstraint *bottomMargin;
@property (nonatomic, strong, readonly) MASConstraint *leadingMargin;
@property (nonatomic, strong, readonly) MASConstraint *trailingMargin;
@property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;

//Convenient Attribute
@property (nonatomic, strong, readonly) MASConstraint *edges;
@property (nonatomic, strong, readonly) MASConstraint *size;
@property (nonatomic, strong, readonly) MASConstraint *center;

Attribute总体来说分为三大类

1、Basic Attribute: 基本属性,支持到iOS6,一般使用得比较多
2、Margin Attribute: 边缘相关属性,支持到iOS8。由于版本要求比较高,一般用得比较少。Margin相关的详细内容请参考iOS8上关于UIView的Margin新增了3个APIs
3、Convenient Attribute: 便捷属性,为了使用方便而特意新增的属性。Autolayout本身没有对应的相关属性


二、MASConstraint

前面我们看到MASConstraintMaker中所有的Attribute都是MASConstraint类型。对于多个Attribute一起写的表达式:

make.left.right.top.bottom.insets(edge);

make.left返回的已经是MASConstraint类型,也就是说right这个Attribute是MASConstraint的属性。
MASConstraint给我们提供了19种Attribute:

//Basic Attribute
@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
@property (nonatomic, strong, readonly) MASConstraint *leading;
@property (nonatomic, strong, readonly) MASConstraint *trailing;
@property (nonatomic, strong, readonly) MASConstraint *width;
@property (nonatomic, strong, readonly) MASConstraint *height;
@property (nonatomic, strong, readonly) MASConstraint *centerX;
@property (nonatomic, strong, readonly) MASConstraint *centerY;
@property (nonatomic, strong, readonly) MASConstraint *baseline;

//Margin Attribute
@property (nonatomic, strong, readonly) MASConstraint *leftMargin;
@property (nonatomic, strong, readonly) MASConstraint *rightMargin;
@property (nonatomic, strong, readonly) MASConstraint *topMargin;
@property (nonatomic, strong, readonly) MASConstraint *bottomMargin;
@property (nonatomic, strong, readonly) MASConstraint *leadingMargin;
@property (nonatomic, strong, readonly) MASConstraint *trailingMargin;
@property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;

细看一下,MASConstraint中的Attribute和MASConstraintMaker完全一样。只是MASConstraintMaker中多了3种Convenient Attribute。

两者Attribute的一致,大大的提升了使用的方便性。使用过程中我们不用再去区分当前属性是MASConstraint还是MASConstraintMaker类型。(事实上没研究他的类型之前,我都不知道他们分别属于2种不同类的属性)


三、UIView(12月7日新增)

我们可以看到在.equalTo(view1.superview.mas_left)里面,superView也有Attribute。我们来看看UIView中有哪些Attribute:

// Basic Attribute
@property (nonatomic, strong, readonly) MASViewAttribute *mas_left;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_right;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottom;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leading;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailing;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_width;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_height;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerX;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerY;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_baseline;

// Margin Attribute
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leftMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_rightMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leadingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerXWithinMargins;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerYWithinMargins;  

可以看出,在UIView中的Attribute和MASConstraint中的几乎一模一样,只是每一个Attribute加了一个mas_前缀。

由于UIView是系统的类,对其扩展属性和方法一般都需要添加自己的前缀,避免跟原有属性和方法冲突。不过他们的意义跟MASConstraint中的Attribute是相同的


四、Relationship

约束表示的是2个item之间的关系,在Autolayout中一共定义了3种关系:=, >=, <=,对应到Masonry中:

- (MASConstraint * (^)(id attr))equalTo;
- (MASConstraint * (^)(id attr))greaterThanOrEqualTo;
- (MASConstraint * (^)(id attr))lessThanOrEqualTo;

相等关系我们一般用的多。那么不相等关系我们什么时候用呢?

假如我有一个Label。Label的长度不能超出父View,如果label中的文字比较短,我希望是文字有多长,Label就有多长。
由于label具有IntrinsicContentSize属性。所以默认情况下,他是文字有多长,Label就有多长。(更多IntrinsicContentSize的内容参见Autolayout的第一次亲密接触)。所以我们只需要设置Label的长度小于父View即可

[label mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.offset(0);
    make.centerY.offset(0);
    make.width.lessThanOrEqualTo(label.superview);
}];  

五、multiplier

multiplier表示Attribute前面的乘数。Masonry提供了2种添加multiplier的方法

//    Sets the NSLayoutConstraint multiplier property
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy;

//    Sets the NSLayoutConstraint multiplier to 1.0/dividedBy
- (MASConstraint * (^)(CGFloat divider))dividedBy;

multipliedBy: 直接设置乘数
dividedBy: 设置乘数的倒数 multiplier = 1.0/dividedBy
一般宽或者高的约束使用multiplier比较多


六、Constant

Masonry提供了4种设置constant的方法

//Modifies the NSLayoutConstraint constant,only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
- (MASConstraint * (^)(MASEdgeInsets insets))insets;

//Modifies the NSLayoutConstraint constant,only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following NSLayoutAttributeWidth, NSLayoutAttributeHeight
- (MASConstraint * (^)(CGSize offset))sizeOffset;

//Modifies the NSLayoutConstraint constant, only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following NSLayoutAttributeCenterX, NSLayoutAttributeCenterY
- (MASConstraint * (^)(CGPoint offset))centerOffset;

//Modifies the NSLayoutConstraint constant
- (MASConstraint * (^)(CGFloat offset))offset;

insets: 用来设置left, right, top, bottom。接受MASEdgeInsets类型值
sizeOffset: 用来设置width, height。接受CGSize类型的值
centerOffset: 用来设置centerX, centerY。接受CGPoint类型的值
offset: 可以用来设置所有的东西。接受CGFloat类型的值

其实一般情况下,我只使用offset....


七、小技巧

1、如果等式2边的Attribute是一样的,我们可以省略等式右边的Attribute
2、如果是等于关系,并且右边的view是父View。连equalTo也可以省略
3、如果equalTo里面传的是NSValue类型,效果跟设置offset是一样的
4、如果offset为0,其实也是可以省略的...

下面所有代码实际效果是一样的:

// 完整的
make.left.equalTo(view1.superview.mas_left).offset(0);

//省略Attribute的
make.left.equalTo(view1.superview).offset(0);

//省略equalTo的
make.left.offset(0);

//使用equalTo替代offset的
make.left.equalTo(@0);

//终极大招,省略所有的... 可惜会有warning:你用getter方法获取回来的值未使用,所以不应该使用"."语法
make.left;  

对于这个警告我们可以将返回值转为空消除:

(void)make.left;    

八、设置或更新约束

对于约束的设置,Masonry提供了3种方法,分别为设置约束、更新约束、重写设置约束

// 设置约束    
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;

// 更新约束
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;

// 重新设置约束
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;  

mas_makeConstraints: 初次设置约束使用。
mas_updateConstraints: 更新约束时使用。如果找不着这条约束,会新增,相当于mas_makeConstraints
mas_remakeConstraints: 重新设置约束。先将view上所有约束移除,再新增约束

注意:mas_updateConstraints只能更新已有约束。如果第一次使用的是left, right设置的相对宽度。更新的时候想换成使用width。不能使用mas_updateConstraints,因为已有约束里面没有width的约束,新增width之后会跟原有left, right约束冲突。此时应该使用mas_remakeConstraints


九、批量设置约束

假设有View1,view2,view3三个View,我们想要他们的宽高都等于CGSizeMake(100, 50)。我们可以对他们进行批量设置:

NSValue *sizeValue = [NSValue valueWithCGSize:CGSizeMake(100, 50)];
[@[view1,view2,view3] mas_makeConstraints:^(MASConstraintMaker *make) {
    make.size.equalTo(sizeValue);
}];

由于我们还要设置view的top,left等位置约束。那可不可以在设置位置的mas_makeConstraints里面批量设置宽高呢?实际是可以的!

//advance set
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    (void)make.top.left;
    make.size.equalTo(@[view2,view3,sizeValue]);
}];

不过需要注意的是。设置约束的时候,view一定是已经被addSubview的,否则会抛异常。所以我们一般在最后一个view上加批量约束


十、Priority

我们知道约束是有优先级的,Masonry给我们提供了4个设置优先级的接口:

 //    Sets the NSLayoutConstraint priority to a float or MASLayoutPriority
- (MASConstraint * (^)(MASLayoutPriority priority))priority;

//    Sets the NSLayoutConstraint priority to MASLayoutPriorityLow
- (MASConstraint * (^)())priorityLow;

//    Sets the NSLayoutConstraint priority to MASLayoutPriorityMedium
- (MASConstraint * (^)())priorityMedium;

//    Sets the NSLayoutConstraint priority to MASLayoutPriorityHigh
- (MASConstraint * (^)())priorityHigh;

priority: 可以设置任意的优先级,接受的参数是0-1000的数字
priorityLow: 设置低优先级,优先级为250
priorityMedium: 设置中优先级,优先级为500
priorityHigh: 设置高优先级,优先级为750

需要注意的是,使用priorityLowpriorityMediumpriorityHigh的时候。不是.priorityHigh,而是.priorityHigh()


十一、key

当约束冲突发生的时候,我们经常为找不到是哪个View冲突的而烦恼,这一堆View是个什么东西呀?

"<MASLayoutConstraint:0x7f8de483fb10 UIView:0x7f8de2f53870.left == UIView:0x7f8de2f586c0.left>",
"<MASLayoutConstraint:0x7f8de4818b50 UIView:0x7f8de2f53870.right == UIView:0x7f8de2f586c0.right>",
"<MASLayoutConstraint:0x7f8de4818870 UIView:0x7f8de2f53870.width == 100>",
"<NSLayoutConstraint:0x7f8de4847e90 UIView:0x7f8de2f586c0.width == 375>"

Will attempt to recover by breaking constraint 
<MASLayoutConstraint:0x7f8de4818870 UIView:0x7f8de2f53870.width == 100>

这时候我们可以设置View的key:

self.view.mas_key = @"self.view";
view1.mas_key = @"view1";

设置之后再看一下,哈哈,现在好多了。可以清晰的知道是哪个view了

"<MASLayoutConstraint:0x7fcd98d17c40 UIView:view1.left == UIView:self.view.left>",
"<MASLayoutConstraint:0x7fcd98d2b2c0 UIView:view1.right == UIView:self.view.right>",
"<MASLayoutConstraint:0x7fcd98d2adb0 UIView:view1.width == 100>",
"<NSLayoutConstraint:0x7fcd98e42050 UIView:self.view.width == 375>"

Will attempt to recover by breaking constraint 
<MASLayoutConstraint:0x7fcd98d2adb0 UIView:view1.width == 100>

大家可能会觉得这样一个一个设置,多麻烦啊!别着急,Masonry提供了批量设置的宏MASAttachKeys
只需要一句代码即可全部设置:

MASAttachKeys(self.view,view1);  

十二、Shorthand(12月7日新增)

在写代码的时候,可能你会感觉有的东西要加mas_前缀,有的东西又不用加,代码风格不统一,而且加mas_前缀还麻烦。

前面介绍过加mas_前缀主要是在扩展系统类的时候为了避免与原有类冲突,这是Apple推荐的做法。不过目前来说,即使不加mas_前缀,也不会有什么问题。所以Masonry提供了不加mas_前缀的方法,只需要你定义几个宏即可。

1、MAS_SHORTHAND
定义MAS_SHORTHAND宏之后。可以使用UIView,NSArray中不带mas_前缀的makeConstraints,updateConstraints,remakeConstraints。以及UIView中不带mas_前缀的Attribute。

2、MAS_SHORTHAND_GLOBALS
默认的equalTo方法只接受id类型的对象。有时候我们想传入一个CGFloat, CGSize, UIEdgeInsets等。还需要将其转化成NSValue对象,比较麻烦。Masonry也考虑到了这种情况。只需要定义MAS_SHORTHAND_GLOBALS宏。就可以直接对equalTo传入基础类型。Masonry自动转化成NSValue对象


『拨开Masonry的衣服』

Masonry的基本使用方法介绍完了,那么我们来看看Masonry的内部到底有些什么东西?

一、结构

Masonry一共有十三个类,我将这13个类分为5个模块:

image


二、Help

Help模块主要是一些辅助的类。

NSLayoutConstraint+MASDebugAdditions:这个类的主要作用是重写NSLayoutConstraintdescription函数。让约束发生冲突的时候,更易读。如果View或者constraint设置了Key,直接用key的值显示到description中。如果没有设置,显示View或者constraint的指针。

ViewController+MASAdditions:提供了ViewController的LayoutGuide相关属性,以便View对齐时使用

MASUtilities:定义了一些公用的宏和属性


三、Shorthand

对于系统原有类(NSArray,UIView)的扩展。Masonry的category方法和属性都加有mas_前缀。这也是Apple建议的做法,避免跟系统原有方法冲突。但是有时候我们可能想用的更方便,不想写mas_前缀(没办法,我就是这么懒...)

NSArray+MASShorthandAdditionsView+MASShorthandAdditions中定义了不带mas_前缀的扩展。这些扩展根据你是否定义了MAS_SHORTHAND宏来确定是否编译。所以你只需要定义MAS_SHORTHAND宏,就可以方便的使用不带mas_前缀的方法,比如:-[view makeConstraints:]


四、Public

Public模块主要是对外暴露的方法。使用者使用Masonry可以直接接触到。

NSArray+MASAdditions:主要有定义和更新约束的方法,如mas_makeConstraints:
View+MASAdditions:除了定义和更新约束的一系列方法之外,还为View增加了mas_top, mas_left等Attribute属性


五、Core

Core模块就是Masonry的核心部分,Masonry的大部分功能都在这4个类里实现

MASConstraintMaker:约束控制器。控制更新,删除,或者新增约束
MASConstraint:约束的基类,虚类。定义了Constraint的基本属性和方法。
MASViewConstraint: 约束的主要实现类。所有对约束使用的功能均在此类中完成
MASCompositeConstraint:约束的集合类。内部有一个数组,可以保存多个MASViewConstraint。对MASCompositeConstraint调用方法实际等于对其内部的所有MASViewConstraint调用方法


六、Property

此模块主要封装了一些MASConstraint持有的属性。为了使用更方便,或者扩展功能

MASViewAttribute:每一个Attribute都有一个View与之对应,为了使用更方便,所以将他们通过一个类封装在一起
MASLayoutConstraint:默认的NSLayoutConstraint是没有Key这个属性的,为了Debug方便。派生一个子类,持有key属性


『实现』

当我们给View添加一个约束的时候到底发生了什么?

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.top.equalTo(view1.superview).offset(20);
}];

我们首先来看make.left.top.equalTo(view1.superview).offset(20);


一、执行"make.left"

MASConstraintMaker类中有一个属性constraints专门用来存储constraint

@property (nonatomic, strong) NSMutableArray *constraints;

当执行make.left的时候, 会将相应的MASConstraint添加到constraints数组中

- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

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

   //调用make.left.top时走入这里将原来的ViewConstraint替换成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;
   }

   // 调用make.left的时候走入这里,将constraint加入到self.constraints中
   if (!constraint) {
       newConstraint.delegate = self;
       [self.constraints addObject:newConstraint];
   }
   return newConstraint;
}

MASConstraintMaker调用Attribute的get方法,最终都会走到-constraint:addConstraintWithLayoutAttribute:中,在这个方法中,通过对应的Attribute生成MASViewConstraint。然后将MASViewConstraint加入到constraints


二、执行".top"

make.left返回的是MASConstraint类型。所以make.left.top是对MASViewConstraint类型调用top方法。

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

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

当执行-addConstraintWithLayoutAttribute的时候,ViewConstraint通过delegate又调回到MASConstraintMaker-constraint:addConstraintWithLayoutAttribute:中。

MASConstraintMaker-constraint:addConstraintWithLayoutAttribute:里,将原来constraints中的MASViewConstraint替换成MASCompositeConstraintMASCompositeConstraint持有top,left 2个属性。对MASCompositeConstraint做操作时候,其内部的所有属性都会执行相应的操作


三、执行".equalTo(view1.superview)"

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

- (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.secondViewAttribute = attr;
                [children addObject:viewConstraint];
            }
            MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
            compositeConstraint.delegate = self.delegate;
            [self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
            return compositeConstraint;
        } else {
            NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}

当执行Relationship的方法时,都会走到-equalToWithRelation中。
在这个方法里面主要是给realationshipsecondViewAttribute赋值:

如果不是数组,直接对realationshipsecondViewAttribute赋值
如果是数组,如:.equalTo(@[view1.mas_left,view2.mas_left]),逻辑上肯定不能是不等关系(>=,<=),所以realationship不用赋值,使用默认值(=)。copy出多个viewConstraint,将secondViewAttribute赋值。然后用多个viewConstraint组成的compositeConstraint替换调原来的viewConstraint


四、执行".offset(10)"

- (MASConstraint * (^)(CGFloat))offset {
    return ^id(CGFloat offset){
        self.offset = offset;
        return self;
    };
}

- (void)setOffset:(CGFloat)offset {
    self.layoutConstant = offset;
}

offset(10)会将10传入到ViewConstraint中,用layoutConstant属性将其存起来。(offset主要影响的是约束里面的constant


五、mas_makeConstraints

看完了make.left.top.equalTo(view1.superview).offset(20);,我们再看看mas_makeConstraints中到底做了什么?

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}

mas_makeConstraints方法很简单,

1、将self.translatesAutoresizingMaskIntoConstraints至为NO。translatesAutoresizingMaskIntoConstraints表示是否将设置的Frame转化为约束。当自己设置约束的时候需要将其置为NO

2、创建出MASConstraintMaker对象

3、通过block抛出到外面设值

4、constraintMaker install

上面的代码我们知道,关键的地方还是在于constraintMaker install


六、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) {
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
    [self.constraints removeAllObjects];
    return constraints;
}

如果需要removeExisting,就把已有的约束remove掉,当调用mas_remakeConstraints的时候会将removeExisting值置为YES
遍历constraints,调用[constraint install]
清空constraints,这里的constraintMaker只是一个零时属性,只是一个工具类,不需要存储。所以用完之后就可以将constraints清空
其实真正关键的地方在[constraint install]


七、constraint install

- (void)install {
    // 1. 已经installed的将不做任何操作
    if (self.hasBeenInstalled) {
        return;
    }

    //2. 从ViewAttribute中剥离出item和attribute
    MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
    NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
    MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
    NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;

    //3. 如果没有secondViewAttribute,默认secondItem为其父View,secontAttribute等于firstLayoutAttribute。
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }

    //4. 创建真正用于Autolayout的约束layoutConstraint
    MASLayoutConstraint *layoutConstraint
        = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                        attribute:firstLayoutAttribute
                                        relatedBy:self.layoutRelation
                                           toItem:secondLayoutItem
                                        attribute:secondLayoutAttribute
                                       multiplier:self.layoutMultiplier
                                         constant:self.layoutConstant];
    //5. 将priority和key赋值
    layoutConstraint.priority = self.layoutPriority;
    layoutConstraint.mas_key = self.mas_key;

    //6. 找到要添加约束的installView
    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;
    }

    //7. 添加或更新约束
    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];
    }
}  

1、如果已经installed就不做任何操作

2、从ViewAttribute中剥离出item和attribute。前面我们介绍过MASViewAttribute的类主要是将item和attribute2个属性封装在了一起。

3、secondViewAttribute的值来自于.equalTo(item.attribute)中的item.attribute。当我们写下类似make.left.offset(10);约束的时候,是没有secondViewAttribute的,这时候默认secondItem为其父View,secontAttribute等于firstLayoutAttribute。这就解释了为什么可以这样写make.left.offset(10);

4、创建真正用于Autolayout的约束layoutConstraint

5、将priority和key赋值

6、找到要添加约束的installView。如果是2个View之间的约束,需要寻找这2个View最接近的共同父View。添加约束

7、添加或更新约束。当调用mas_updateConstraints的时候updateExisting=YES。这时候会查找是否有已经存在的约束。有就更新,没有就添加。如果是mas_makeConstraints或mas_remakeConstraints,则直接添加


『Extension』

仅仅将代码结构和基本实现过程解析了一下,更多实现细节还需要大家自己去阅读源码

说实话,Masonry的代码写得真漂亮,不管是代码格式规范,还是设计模式。看起来简直是一种享受。建议大家阅读。

Autolayout的第一次亲密接触也更新了一些东西。没阅读过或者阅读时间比较早的朋友可以再看看~


『Reference』

Masonry源码
Autolayout的第一次亲密接触
iOS8上关于UIView的Margin新增了3个APIs

推荐阅读更多精彩内容

  • Masonry是一个轻量级的布局框架,拥有自己的描述语法,采用更优雅的链式语法封装自动布局,简洁明了并具有高可读性...
    3dcc6cf93bb5阅读 845评论 0 1
  • (一)Masonry介绍 Masonry是一个轻量级的布局框架 拥有自己的描述语法 采用更优雅的链式语法封装自动布...
    杨梦鸽阅读 1,452评论 0 3
  • Autolayout就像一个知情达理,善解人意的好姑娘,可惜长相有点不堪入目,所以追求者寥寥无几。所幸遇到了化妆大...
    小笨狼阅读 23,051评论 28 225
  • iOS_autoLayout_Masonry 概述 Masonry是一个轻量级的布局框架与更好的包装AutoLay...
    指尖的跳动阅读 769评论 1 4
  • 一、Masonry介绍 之前我们在屏幕适配的章节中学习过AutoLayout的使用,但那都是在可视化界面上进行添加...
    无沣阅读 1,336评论 0 1