Masonry框架源码分析

Masonry框架源码分析

相信大多数iOS开发者对Masonry框架并不陌生 , 本文是笔者通读Masonry的代码之后的一篇总结, 也希望可以帮助大家更好的理解该框架. 怎奈笔者才疏学浅, 如有遗漏或错误也欢迎大家评论区指出, 大家一起进步!

iOS布局的演进

在说Masonry, 先简单介绍一下iOS开发屏幕适配的发展过程. 在iPhone3Gs/4/4s时代, 手机屏幕尺寸都是一样的, 对于开发者来说基本不用适配,彼时的屏幕布局基本都是采用frame, 但是随着iPad的出现, frame布局便不能满足需求, 苹果开始推出AutoResizing布局, 这种布局核心内容就是: 以父容器为参照物来对子空间进行frame布局, frame不再是直接写死的值, 而是可以根据父视图的大小变化, 但是这种布局方式的缺点也很明显, 就是不能设置兄弟视图之间的关系, 所有苹果煞费苦心的推出了AutoLayout, AutoLayout的出现基本弥补了AutoResizeing不足, iOS开发的屏幕适配变得更加轻松.

苹果原生AutoLayout布局与Masonry比较

虽然苹果的初衷很好, 但是无奈苹果的NSLayoutConstraint布局实在是太过于臃肿了, 所以在github开始涌现出各种各样的三方布局框架, 其中就有今天的主角Masonry. 笔者截取了部分布局代码大家感受一下

 UIView *blueView = [[UIView alloc]init];
 blueView.backgroundColor = [UIColor blueColor];
 [self.view addSubview:blueView];
  
    
  ///原生自动布局方式
 //去掉aotoReszing
 blueView.translatesAutoresizingMaskIntoConstraints = NO;  
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
    
NSLayoutConstraint *left= [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0];
     
NSLayoutConstraint *right = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant: 0];
     
NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0  constant:0];
 
    [self.view addConstraint:top];
    [self.view addConstraint:left];
    [self.view addConstraint:right];
    [self.view addConstraint:bottom];

上面只是对一个空间进行的代码, 而对于iOSAPP来说, 每个页面数个或数十个View都是很常见的事, 如果采用这种布局方法, 估计整个类里面全都是布局代码了, 这必然会给代码的阅读与维护带来很大的不便.

其实苹果还有一种自动布局的方式相对与上面的布局方法稍微好一点, 那就是VFL布局, 感兴趣的可以去了解一下, 这里贴上我之前总结的一个帖子 iOS开发之VFL布局总结

然后大家看一下使用Masonry实现上面同样的功能的代码

 UIView *blueView = [[UIView alloc]init];
 blueView.backgroundColor = [UIColor blueColor];
 [self.view addSubview:blueView];
 
 
//Masonry布局实现
[v mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.left.bottom.right.equalTo(self.view);, 
}];

综合以上两个示例对比, 高下立见, 使用Masonry布局代码简洁美观, 使用原生布局代码臃肿不堪,所以如果你是使用代码来进行自动布局, 有什么理由不用Masonry呢!

Masonry源码分析(正题)

Masonry框架整体来说并不是一种新的布局方式, 它仅仅是对NSLayoutConstraint做了一层封装, 所以对于框架背后必然还是要进行一堆原生的代码操作, 所以我们才需要进行一窥究竟!

我们按照调用顺序来介绍来一一介绍

1. View+MASAdditions

View+MASAdditions 此文件看起来内容很多, 但是仔细观察, 其实主要分为两部分, 第一部分就是给View扩展属性(mas_left, mas_right, 等属性), 第二部分就是给View扩展方法( mas_makeConstraints: 等方法), 为了便于分析文件精简如下

@interface MAS_VIEW (MASAdditions)

@property (nonatomic, strong, readonly) MASViewAttribute *mas_left;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_xxx;
...


///设置约束
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
///更新约束
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
///重设余数
- (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;

@end

由于后面三个方法实现几乎一样, 所以这里只对mas_makeConstraints方法进行简单的分析

@implementation MAS_VIEW (MASAdditions)

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

...

@end

要使用AutoLayout第一件事就是要关闭当前View的translatesAutoresizingMaskIntoConstraints,

接着通过工厂类MASConstraintMaker生成了一个constraintMaker, 这个也就是我们在Block回调中调用make实例, 然后执行Block代码块, 在代码块中make分别去执行.top, .left等操作完成 最后执行install操作;

2. MASConstraintMaker

在执行block(constraintMaker) 实际上就是再执行make.top.left.bottom.right.equalTo(self.view);

我们来看make.top的实现原理, 这里个也是Masonry巧妙的地方, 利用block的特性实现了链式调用.

//本类中left, leading, top, width等调用的都是同一个方法 addConstraintWithLayoutAttribute
- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
///此方法直接调用了(constraint:addConstraintWithLayoutAttribute:)
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

///添加约束到self.constraints数组中, 此处也是MASConstraintDelegate的代理实现, 也用做本类的添加约束
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    //MASViewConstraint是MASConstraint子类
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    ///判断constraint是否是MASViewConstraint,  如果是组合MASCompositeConstraint属性, 则说明使用者有调用错误, 因为组合属性后面不应该在跟一个新属性, 所以在这个方法里面过滤掉了组合属性的问题
    if ([constraint isKindOfClass:MASViewConstraint.class]) {//如果constraint不是第一次调用则他应该是一个组合约束了, make.top.left.right
        //replace with composite constraint
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    //在添加单个属性时constraint参数都为nil 所以以上两个分支都忽略
    
    ///判断constraint是否是nil;
    if (!constraint) {//如果是第一次则添加一个新属性进来
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }

    return newConstraint;
}


按照顺序分析, 当调用make.top是最终会来到 - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute 方法中, 这个方法做了一下几件事

第一件事生成viewAttribute属性, 然后通过viewAttribute生成newConstraint这两个实例所对应的类在后面介绍, 这里先标记上, 因为调用constraint参数传递的是nil 所以这里第一个if语句不执行, 直接执行第二个if分支里, 在第二个分支中,给新创建的newConstraint设置代理, 然后将newConstraint添加到maker所持有的数组中, 到这里第一个属性top就完成了记录.

整体来看就是调用top方法会生成一个top的约束, 然后将这个约束添加到maker所持有的数组constraints中

3. MASConstraint

MASConstraint实际上是一个抽象类, Masonry巧妙地使用了面向对象的多态特性进行编程. MASConstraint类中定义了很多抽象方法都需要在子类中实现, 这里摘取几个例子如下

#pragma mark - Abstract

//MASMethodNotImplemented() 这个宏定义采用了抛出错误的方法
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy { MASMethodNotImplemented(); }

- (MASConstraint * (^)(CGFloat divider))dividedBy { MASMethodNotImplemented(); }

- (MASConstraint * (^)(MASLayoutPriority priority))priority { MASMethodNotImplemented(); }

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { MASMethodNotImplemented(); }

- (MASConstraint * (^)(id key))key { MASMethodNotImplemented(); }

上面摘取的这些方法是MASConstraint中的实现, 我们可以看到方法体都是直接调用了一个宏MASMethodNotImplemented(), 我们们顺着这个宏发现, 实际上就是一个抛出错误的处理, 如果子类不实现这个方法, 则调用时就会来到父类的这个方法中最终抛出错误, 间接达到java中抽象类的效果! 需要注意的是这个错误是运行时错误, 所以如果不调用依然是无法发现错误的.

这里我们依然使用 make.top.left.bottom.right.equalTo(self.view) 这个例子来将进行分析, 第2部分讲到make.top最终是调用到MASConstraintMaker中的 - (MASConstraint )constraint:(MASConstraint )constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute 而make.top的返回值大家可以看到实际上是MASConstraint, 因此当make.top再接着调用.left的时候已经变成了MASConstraint** 的实例进行.left的调用, 然后我们来看MASConstraint中的left方法(这里再强调一下MASConstraint 中的链式调用, 比如.left 实际上是走的left的Getter方法, OC的语法糖可以让我们实现用点语法替代get方法, 而该方法返回值又是MASConstraint 类型, 所以可以实现链式调用)

@implementation MASConstraint


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

- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}

- (MASConstraint *)right {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}

- (MASConstraint *)bottom {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}

///这里是一个抛出错误的空实现
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
    MASMethodNotImplemented();
}


@end

我们可以看到.left, .top等方法实际上最终都会调用 addConstraintWithLayoutAttribute方法, 而在MASConstraint中该方法实际上是一个抽象方法, 并无实质的内容实现, 所以很明显这个问题我们需要放到子类实现中来说了

  • MASViewConstraint

    看头文件实现, 我们可以得知MASViewConstraint 继承了 MASConstraint, 因此MASViewConstraint拥有父类的所有特性, 因为父类在上面已经介绍过, 这里只说明一下MASViewConstraint 特有的东西

    @interface MASViewConstraint : MASConstraint <NSCopying>
    
    ///第一个View的属性
    @property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute;
    ///第二个view的属性
    @property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute;
    ///构造方法
    - (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute;
    
    @end
    
    

    因为之前还未介绍MASViewAttribute, 所以这里先大概说一下, MASViewAttribute类实际上是约束的模型, 主要用来记录约束内的关系, 如果不太明白, 你可以看一下第4部分MASViewAttribute的介绍, 然后再回来看这个.

    我们看到MASViewConstraint中有两个 属性firstViewAttribute 和 secondViewAttribute, 关于这两个属性我们来看一下原生的自动布局实现我们就明白了

    NSLayoutConstraint *left= [NSLayoutConstraint constraintWithItem:blueView 
                                                          attribute:NSLayoutAttributeLeft     
                                relatedBy:NSLayoutRelationEqual 
                                   toItem:self.view 
                                  attribute:NSLayoutAttributeLeft 
                               multiplier:1.0 
                                   constant:0];
    

    通过上面的代码片段, 我们可以发现, 约束是有两部分组成的, 也即是第一个view的某个约束属性, 和第二个View的某个约束属性的关系, firstViewAttribute实际上就是用来存储blueView和约束NSLayoutAttributeLeft secondViewAttribute 中记录的是self.view和对应的NSLayoutAttributeLeft.

    在描述MASConstraint 中提到.left/.top等这些方法实际上最终会调用到 - *(MASConstraint )addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute , 而这个方法是抽象方法,在MASConstraint中并没有实际实现, 所以我们接着说这个方法

    @implementation MASViewConstraint
    
    #pragma mark - attribute chaining
    - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
        NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
        //调用代理来完成属性的添加
        return [self.delegate constraint:self 
        addConstraintWithLayoutAttribute:layoutAttribute];
    }
    
    @end
    
    

    我们可以看到MASViewConstraint中- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute, 直接调用了代理方法 constraint:addConstraintWithLayoutAttribute:, 看到这里可能稍微有点绕, 比如这个delegate是谁, 在哪里设置的这个delegate, 没关系慢慢分析还是可以找到线索的, 我们再回到 make.top的最终调用, 如下

    @implementation MASConstraintMaker
    
    ///添加约束到self.constraints数组中, 此处也是MASConstraintDelegate的代理实现, 也用做本类的添加余数
    - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
        MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
        //MASViewConstraint是MASConstraint子类
        MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
        ///判断constraint是否是MASViewConstraint,  如果是组合MASCompositeConstraint属性, 则说明使用者有调用错误, 因为组合属性后面不应该在跟一个新属性, 所以在这个方法里面过滤掉了组合属性的问题
        if ([constraint isKindOfClass:MASViewConstraint.class]) {//如果constraint不是第一次调用则他应该是一个组合约束了, make.top.left.right
            //replace with composite constraint
            NSArray *children = @[constraint, newConstraint];
            MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
            compositeConstraint.delegate = self;
            [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
            return compositeConstraint;
        }
        //在添加单个属性时constraint参数都为nil 所以以上两个分支都忽略
        
        ///判断constraint是否是nil;
        if (!constraint) {//如果是第一次则添加一个新属性进来
            newConstraint.delegate = self;
            [self.constraints addObject:newConstraint];
        }
    
        return newConstraint;
    }
    
    @end
    

    仔细看, 在方法内部最后一个if分支中正是给newConstraint设置代理的代码, 也就是说 MASViewConstraint 的代理实际上就是MASConstraintMaker, 所以当make.top再去调用.left的时候, 实际上最终还会来到MASConstraintMaker中上面这个方法来添加属性, 我们来一步一步的分析这个方法, 由于MASViewConstraint中 调用代理方法时第一个参数constraint并不为nil 所以, 上面这个方法调用会和在MASConstraintMaker中直接调用有所不同.

    make.top的返回值是一个MASViewConstraint类型, 所以这里直接进入了第一个分支, 在第一个分支中创建了一个MASCompositeConstraint类型的实例(这个类接下来会分析), 然后return, 结束了方法调用!

  • MASCompositeConstraint

    这个类是MASViewConstraint子类一个约束组合类, 作用就是把多个约束组合在一起, 当 make.left.top执行结束后实际返回值类型是MASCompositeConstraint 接下来再接着执行 make.left.top.bottom时, 实际上是执行的MASViewConstraint中的bottom方法, 最终会调用子类MASCompositeConstraint 里面的 - (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute 方法, 我们来看这个方法的实现

    @implementation MASCompositeConstraint
    
    
    - (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
        id<MASConstraintDelegate> strongDelegate = self.delegate;
        //这里只是通过代理Maker中的方法获取一个新约束, 然后添加到childConstraints数组中
        MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
        newConstraint.delegate = self;
        [self.childConstraints addObject:newConstraint];
        return newConstraint;
    }
    
    @end
    

    通过分析上面的代码我们可以发现, 最终还是要调用 MASConstraintMaker 中的添加约束的方法中, 这里就不再重复, 最后返回的依然是 MASCompositeConstraint类型的约束, 然后接着执行 make.left.top.bottom.right 返回MASCompositeConstraint类型的实例;

接下来, 分析equalTo方法, 在父类MASConstraint中定义并实现了equalTo方法, 但是方法实现实际上调用的是equalToWithRelation方法, 而这个方法在MASConstraint类中做了一个空实现, 并且要求子类分别实现, 所以我们分别来看 MASViewConstraint 和 MASCompositeConstraint中的实现


@implementation MASViewConstraint
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if ([attribute isKindOfClass:NSArray.class]) {//TODO: 暂时还没有找到多个属性的调用, 有哪位看懂了这个分支, 可以评论区交流, 
            
            NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
            NSMutableArray *children = NSMutableArray.new;
            for (id attr in attribute) { //遍历传入属性
               //copy当前属性,
                MASViewConstraint *viewConstraint = [self copy];
                viewConstraint.layoutRelation = relation;
                viewConstraint.secondViewAttribute = attr;
                ///将多个copy的值加入数组
                [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;
            //设置第二个属性的值, 在secondViewAttribute的set方法中去设置值 attribute为id类型
            
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}
@end




@implementation MASCompositeConstraint

#pragma mark - NSLayoutRelation proxy
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attr, NSLayoutRelation relation) {
        for (MASConstraint *constraint in self.childConstraints.copy) {
            constraint.equalToWithRelation(attr, relation);
        }
        return self;
    };
}

@end

观察上面的两个类中的equalToWithRelation方法的实现, 我们可以发现MASCompositeConstraint 的实现 最终就是用自己持有的childConstraints中的各个constraint去掉用equalToWithRelation, 所以这里最终还是执行到MASViewConstraint中去, 所以我们只需要看MASViewConstraint中equalToWithRelation的实现!

由于要实现.equalTo(xxx)这样的函数式编程, 所以equalToWithRelation内部返回的是有个有参数的block, 这样外部调用时就可以达到函数式编程的效果, block返回值是MASConstraint类型以达到链式调用的效果.

bolck内部有个if分支 判断条件是[attribute isKindOfClass:NSArray.class], 这里笔者还有一点疑惑, 暂时没有找到什么时候会来到这个分支, 所以也请各位看官读者指点迷津, 在分支的else语句里 执行了self.secondViewAttribute = attribute; secondViewAttribute属性前面已有描述这里不再赘述, 不过有一点需要着重说一下, 就是secondViewAttribute的Setter方法, 这里mas的作者巧妙的实现了Setter方法, 我们可以来做一下分析

///这个set方法比较特殊, 传进来的并不是属性的类型, 需要在set方法中转换
- (void)setSecondViewAttribute:(id)secondViewAttribute {
    if ([secondViewAttribute isKindOfClass:NSValue.class]) {///如果是NSValue则_secondViewAttribute直接取值
        [self setLayoutConstantWithValue:secondViewAttribute];
    } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {///如果是UIView则取用和第一个View相同的值
        _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);
    }
}

在这个方法中对secondViewAttribute进行了三个种类判断, 如下:

1.判断是不是NSValue类型则说明是直接对第一个约束设置了常量, 例如: make.width.equalTo(@100);

2.判断是不是MAS_VIEW(是一个宏, 在iOS开发时对应的是UIView), 如果是, 则创建一个secondViewAttribute, 而secondViewAttribute的layoutAttribute和firstViewAttribute的layoutAttribute,

3.判断是不是MASViewAttribute, 如果是则直接设置给属性secondViewAttribute;

综合以上三点, 所以我们的equal()方法中可以是NSNumber, 也可以是view.mas_xxx, 也可以直接是一个view类型

通常调用Masonry还有一种形式是这样的: make.left.equalTo(view).offset(10); 前面内容都有分析, 现在独看.offset()方法; 在MASConstraint类中offset定义依然是一个block属性, 但是这里稍有不同, .offset()实质上调用的是offset的getter方法如下:

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

看完以后可能会有点蒙, 尤其是block内部的实现 self.offset = offset, 为何一个CGFloat的值可以赋值给block类型的属性呢? 这里得说一下, 实际上 self.offset = offset中的self.offset是在调用offset的setter方法, 而在MASConstraint类中的setter方法依然是一个抽象方法, 本类中进行的是空实现, 所以在MASViewConstraint 和MASCompositeConstraint 两个子类中分别进行了实现, 而两个子类中的实现最终都会来到MASViewConstraint中setOffset方法如下:

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

此方法实质就是记录了当前约束的偏移量, 以待后续使用

4. MASViewAttribute

MASViewAttribute是用来记录view和要指定的约束的, 它的内容较少, 比较简单, 包含当前view属性,和当前view的指定约束

例如第一个item的left约束, 通过构造方法可以生成MASViewAttribute 实例;

5. 约束的安装

通过以上四个部分的分析, 我们已经完成了block代码块中的所有分析, 接下来继续来看 View+MASAdditions 中的 mas_makeConstraints方法, 在方法内部执行完block之后, 紧接着执行 [constraintMaker install]; install方法如下

@implementation MAS_VIEW (MASAdditions)

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


@end

install方法, 首先判断该约束是否已经存在, 如果存在则需要先uninstall, self.removeExisting默认是NO, 因为在执行mas_updateConstraints或者mas_remakeConstraints方法中将其设置为YES, 而这两个方法最终都会调用insatll方法; View+MASAdditions 分类中install方法最后就是遍历 maker持有的constraints数组, 分别进行安装由于数组中的约束可能MASViewConstraint类型, 也可能是MASCompositeConstraint类型, 所以再这两个类中分别有实现install方法, 不过最终调用还是来到MASViewConstraint中的install方法, 这里我们只对MASViewConstraint中的install方法进行分析, 方法实现如下

- (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 *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) {
        self.installedView = self.firstViewAttribute.view;
    } else {
        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;
        self.layoutConstraint = existingConstraint;
    } else {
        [self.installedView addConstraint:layoutConstraint];
        self.layoutConstraint = layoutConstraint;
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}

核心内容就是将MASViewConstraint中所持有的数据, 进行解析 ,并调用系统的自动布局方法进行设置约束, 这里不做赘述, 但看下面这段

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

我们调用系统的自动约束布局时, 需要清楚将约束安装到哪个view上, 而上面这段正式找到要安装的view, 按照系统自动约束的规则, 如果是size, 宽高约束需要作用的view本身, 如果是上下左右约束需要找到合适的view上, 所以通过以上判断获取到合适的installedView, 第一个分支中mas_closestCommonSuperview方法是求两个视图的最近父视图, 这个方法可以着重看一下, 这里不再赘述!

总结

通过以上分析, 我想对各位读者分析Masonry框架有很大帮助, 还有不少细节需要读者自行分析! 最后,笔者在总结一下Masonry中的重点内容:

  • 链式编程/函数式编程

    这个在Masonry中多数方法都是采用的这个编程方式, 虽然框架内部实现相对复杂, 但是对于调用这来说极其简洁明了, 这个是一个优秀框架最难得的地方; 由于OC的方法调用是用方括号实现, 所以在实现链式编程时相对比较麻烦一点, 但是作者巧妙使用block, 以及OC中Getter和Setter的语法糖(点语法), 在外形上实现了链式编程的效果, 这一点值得学习和深思!

  • 抽象类实现

    由于Xcode并没有抽象类的校验, 所以抽象类类中抽象方法极其容易忽略或者忘记, Masonry作者采用了OC的多多态特性, 在父类中进行了抛出错误的空实现一次来达到父类方法子类必须实现的效果!

  • Setter和Getter方法灵活应用

    在本文第3部分末尾有描述, offset 的 get方法获得获取的Block类型, 而Setter方法传入的是CGFloat, 这里实际上只是巧用OC语法糖实现了self.offset = offset 看起来好像类型都不匹配的代码!

更多优质文章和内容请关注笔者公众号(码农的奋斗日记: lifeRecording)

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