Masonry框架源码分析

2023-05-16

相信大多数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)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Masonry框架源码分析 的相关文章

  • 盘点 | 2023年最值得学的编程语言TOP 5,Python再度夺冠!

    前言 在技术的推动发展中 xff0c 编程语言的作用功不可 目前在技术领域约有600种语言 xff0c 人们对编程语言的认识和掌握情况每年都在变化 与此同时 xff0c 新兴的编程语言往往具有引人注目的元素和功能 2023年热门的编程语言有
  • python socke ftp功能实现 shell命令,上传,下载

    python socke ftp功能实现 shell命令 xff0c 上传 xff0c 下载 网上教程多 xff0c 但是都不全 xff0c 只有下载代码 本人练习 xff0c 附加了注释 xff0c 帮助新人练习 一定要吃透socket来
  • jumpserver 修改源码实现密钥+密码

    背景 云主机登录 密钥 43 密码 xff0c jumpserver登录只能配置自动登录 xff0c 或者手动登录不能满足 修改 如果设置密码为chongzhi 就必须重新录入密码 vi opt coco coco proxy py 39行
  • uml学习过程7-顺序图

    描述强调消息时间顺序的交互图 对象 对象生命周期 消息 对象创建与销毁 动态建模 xff1a 顺序图 圆柱 xff1a 调用类 不表达逻辑判断 xff1a 例如 密码错误 xff0c 非空判断 这些用于活动图表达
  • springmvc错误跳转页面

    在做一个项目的时候 xff0c 为了界面美观及用户体验 xff0c 我们往往会设计自己的错误跳转页面 xff0c 而不是直接展示给用户一堆错误码 xff0c 为此我们需要配置自己的错误跳转页面 1 项目结构 2 web xml lt DOC
  • 下载进度条

    span class token doctype span class token punctuation lt span span class token doctype tag DOCTYPE span span class token
  • 域名绑定到github主页

    最近在通过网上的教程搭建自己的github主页 xff0c 虽然现在也是半成品 xff0c 但是其中有一些步骤以及参考的连接还是值得分享一下的 首先在godaddy上购买的 com域名 xff0c 因为看见大家都说购买国内的域名需要备案之类
  • c++面试宝典

    目录 一 多线程 二 指针 三 字符串 四 面向对象 五 基本用法 六 c 43 43 11 七 算法 c 43 43 面试必考多线程 xff0c 内存 xff08 智能指针 xff09 xff0c 常见算法 xff0c 设计模式 一 多线
  • ssh远程执行命令的方法

    设置免密登录之后 xff0c 通常ssh remote ip command 就可以方便的执行远程命令 如果遇到包含单引号或者双引号的命令 xff0c 执行不成功 xff0c 如 xff1a awk F 39 39 39 print 1 3
  • [海外上架必备][Android]Google原生代码崩溃符号生成的问题

    默认情况下 xff0c 原生代码库已从应用的发布 build 中移除 此移除操作包括移除应用所使用的所有原生库中包含的符号表及调试信息 移除原生代码库会显著缩减大小 xff1b 但是 xff0c 由于缺少信息 xff08 例如类和函数名称

随机推荐

  • “应版权方要求,文件无法下载”的解决方案

    应版权方要求 xff0c 文件无法下载 的解决方案 参考文章 xff1a xff08 1 xff09 应版权方要求 xff0c 文件无法下载 的解决方案 xff08 2 xff09 https www cnblogs com easyide
  • 分布式系统核心—日志

    分布式系统的核心组件 日志 有时也叫write ahead logs commit logs 或者事物 logs 通常指在应用所有的修改之前先写入日志 xff0c 一般会将重放日志 撤销日志都写进去 NoSQL数据库 KV存储 Hadoop
  • ctags使用方法 ctags的使用方法

    用 ctags 看代码时 xff0c 检索函数及变量使用的文件是 tags 文件 有时我们会定制检索的文件范围 xff0c 这时候就可以通过 ctags 命令的一些参数来控制 tags 文件的内容 1 xff1a 递归检索当前目录以下所有默
  • AFNetworking 介绍和简单实用

    AFNetworking github AFNetworking AFNetworking 是一个网络请求封装框架 xff0c 使用简单 xff0c 功能强大 xff1b 在AFNetworking 3 x版本 通过封装NSURLSessi
  • Tomcat端口冲突的解决方法

    1 输入以下两条命令 span class ruby span class hljs number 1 span dos窗口中输入 xff1a netstat ano findstr span class hljs number 8080
  • Spring 框架介绍和使用

    微信公众号 xff1a 运维开发故事 xff0c 作者 xff1a 老郑 历史的选择 Spring 作为一个基础的框架 xff0c 是在 Java EE 开发历史中 xff0c 是成千上万公司选择 单独使用 Spring 的非常少了 xff
  • xshell无法调用图形化的解决方法

    在xshell无法调用图形化界面 xff0c 使用VNC服务 xshell中调用图形化界面需要2个地址互通 xff0c 只有一端通无法传输图形化界面 xff08 本地没有获取IP xff09 但是vnc只需要和服务器端连通即可 1 首先我们
  • python3+requests请求方式application/x-www-form-urlencoded传递数组Arrary

    python3 43 requests传递比较简单的key value格式数据比较简单 导入 requests 包 import requests 表单参数 xff0c 参数名为 fname 和 lname myobj 61 39 fnam
  • 用Java远程执行shell命令出现java: command not found

    一 问题发现 xff1a 在使用jsch远程调用shell命令时 xff0c 提示java command not found 这个错误的意思是linux的环境变量里没有配置JAVA HOME的内容 但是我在Linux上查看了一下环境变量
  • Android Design Support Library

    1 Navigation View 对于应用程序 xff0c 它代表着一个标准的导航菜单 菜单内容可以由菜单资源文件填充 NavigationView通常放在一个DrawerLayout里面 lt xml version 61 34 1 0
  • 日常问题:解决nested exception is org.apache.ibatis.executor.ExecutorException: No constructor found问题

    今天在调试上周编写好得代码程序的时候 xff0c 在执行到mybatis获取某行数据转换成自定义的类型时 xff0c 抛出了异常 xff1a nested exception is org apache ibatis executor Ex
  • Abort message: ‘FORTIFY: FD_SET: file descriptor 1070 >= FD_SETSIZE 128‘

    问题现象 压力测试骁龙相机 xff0c 发现camera provicer 进程崩溃 无法正常打开相机 xff0c 只有重新启动设备 相关的log xff1a 03 23 08 17 08 592 15634 15634 F DEBUG s
  • 滚动校验(Rolling Checksum)算法

    滚动校验 Rolling Checksum 算法 Rsync中使用了一种滚动检验 Rolling Checksum 算法 xff0c 用于快速计算数据块的检验值 它是一种弱校验算法 xff0c 采用的是Mark Adler的adler 32
  • Android GMS认证总结01

    测试项 失败项 备注 GTS com google android media gts WidevineYouTubePerformanceTests testL3Cenc720P30 pass com google android per
  • ZYNQ 在linux 通过AXI_GPIO操作电平

    在petalinux 通过AXI GPIO操作电平 以zynq为例 xff0c vivado工程 xff1a axi gpio n都是选择的一位输出 xff1a 管脚约束 xff1a set property SEVERITY Warnin
  • FSK,PSK,ASK,BPSK调制

    信号调制常用的三种基本方法是 xff1a 调幅 调频和调相 1 振幅调变 xff0c 简称为调zhi幅 xff0c 通过改变输出dao信号的振幅 xff0c 来实现传送信息的目的 一般在调制端输出的高频信号的幅度变化与原始信号成一定的函数关
  • ZYNQ移植vxworks系统

    版本 xff1a ZYNQ7010 xff0c VxWorks 6 9 ZYNQ PL端有灵活性好 xff0c 资源丰富 xff0c 可反复编程速度快的优势 xff0c 通过 PS的外设并行 AXI总线外挂 PL接口 xff0c 使用 FP
  • OpenCV-Python 3.X: cv2.xfeatures2d 无法使用问题解决

    由于专利的问题surf和sift特征已经被移到xfeatures2d里面 xff0c 这个模块需要安装opencv contrib python opencv的dnn模块可以加载深度学习模型 xff0c 但是dnn需要3 4以上 xff0c
  • 旧款Mac开启随航(sideCar)功能

    看到这个标题 不得不吐槽苹果 命名软硬件都可以支持 但是故意让旧设备不支持这个功能 真是鸡贼 吐槽完毕 本文参考博客 http dev zeppel eu luca SidecarCorePatch 里面是英文版的 所以我总结翻译以下几点
  • Masonry框架源码分析

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