Masonry源码分析笔记

Masonry是Objective-C中一款非常火爆的替代原生AutoLayout的第三方框架;相比原生而言,使用起来更简单、优雅。

其实,项目中很多地方都有用到,之前一直没有深入去探究其中的原理,最近得空,来做个简单的分析:

注意:分析之前请先查看MASUtilities.h,这里面重新定义了系统的一些类名(或者叫起别名),以方便理解。

 

使用方法

//先添加到父视图
[self.view addSubview:_statusLable];

//添加约束    
__weak typeof(self) weakSelf = self;
[self.statusLable mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.mas_equalTo(10);
    make.right.mas_equalTo(-10);
    make.height.mas_equalTo(21);
    make.top.equalTo(weakSelf.tipImage.mas_bottom).with.offset(10);
}];    

其中,离我们最接近的就是这个mas_makeConstraints方法了,我们也由此入口来深入展开。

很明显,这个方法的参数是个block,没有返回值,携带一个MASConstraintMaker *类型的参数;然后我们就在这个block内部一通设置,就实现了相关布局,这也太神奇了吧~

其实,作者是把简单的用法留给了用户,复杂的功能自己实现了。

首先,通过分类(View+MASAdditions.h)的方式,给view添加了一系列的方法和属性(mas_left、mas_top等等)

//制作约束
- (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;

方法内部:

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    //确定是否将视图的自动调整遮罩转换为“自动布局”约束
    self.translatesAutoresizingMaskIntoConstraints = NO;
    //生成制作约束类的实例
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    //执行回调,暴露出去,用于设置相关约束
    block(constraintMaker);
    //安装所有约束并return
    return [constraintMaker install];
}

首先,我们需要重点关注

block(constraintMaker);

所有我们之前设置的相关约束,都是通过这个函数传进来的。而这个MASConstraintMaker把约束都存了起来。

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

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

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

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

我们发现,使用过程中的.语法挺特别,作者在这里用到了链式调用,我们用以下使用方法来具体分析:

make.left.mas_equalTo(10);

make.left生成一个约束属性,并保存在make中,然后返回一个MASConstraint实例,供我们调用

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

然后接着调用这个实例的equalTo方法

/**
 返回block(返回值为MASConstraint类型,参数id类型)
 */
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

上边代码猛地看起来有点儿晕,我们不妨慢慢分析、消化,这里建议倒着推:equalTo方法返回了一个block,那我们在调用方法时,其实就是间接调用了以下block

^id(id attribute) {
    return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};

也就间接调用了equalToWithRelation函数,而我们传入的attribute,此时被equalToWithRelation函数执行,然后返回的MASConstraint实例方便我们继续设置。真是妙极了!block也被玩出了?

 

我们接着分析equalToWithRelation方法,基本就是通过传入的属性、关系,生成约束,封装、保存,返回。

里边涉及的MASViewConstraint、MASCompositeConstraint其实都是MASConstraint的子类

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        //如果attribute是数组类型
        if ([attribute isKindOfClass:NSArray.class]) {
            //已经处理过,则报错
            NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
            NSMutableArray *children = NSMutableArray.new;
            //获取所有的属性,生成MASViewConstraint
            for (id attr in attribute) {
                MASViewConstraint *viewConstraint = [self copy];
                viewConstraint.layoutRelation = relation;
                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;
            //设置相关联的view属性
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}

我们需要重点关注一下secondViewAttribute方法:

- (void)setSecondViewAttribute:(id)secondViewAttribute {
    //根据你传入的值的种类赋值
    if ([secondViewAttribute isKindOfClass:NSValue.class]) {
        //NSValue类型,则直接赋值,像make.left.mas_equalTo(10);
        [self setLayoutConstantWithValue:secondViewAttribute];
    } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
        //view,则生成新的MASViewAttribute,赋值给_secondViewAttribute,像make.top.equalTo(self.nameLabel);
        _secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
    } else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
        //像make.left.equalTo(view.mas_right);
        //
        MASViewAttribute *attr = secondViewAttribute;
        if (attr.layoutAttribute == NSLayoutAttributeNotAnAttribute) {
            _secondViewAttribute = [[MASViewAttribute alloc] initWithView:attr.view item:attr.item layoutAttribute:self.firstViewAttribute.layoutAttribute];;
        } else {
            _secondViewAttribute = secondViewAttribute;
        }
    } else {
        NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
    }
}

最后就是安装所有约束(及调用maker的install方法)了:

- (NSArray *)install {
    //有此标记,表示是重新制作约束
    if (self.removeExisting) {
        //获取所有已经安装的约束
        NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
        //逐个移除
        for (MASConstraint *constraint in installedConstraints) {
            [constraint uninstall];
        }
    }
    
    //copy一份,用于返回所有已经安装的约束
    NSArray *constraints = self.constraints.copy;
    //安装新约束
    for (MASConstraint *constraint in constraints) {
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
    //移除所有约束
    [self.constraints removeAllObjects];
    
    //返回所有已安装的约束
    return constraints;
}

需要深入了解一下MASConstraint子类的install方法:

- (void)install {
    //已安装
    if (self.hasBeenInstalled) {
        return;
    }
    
    //可以响应isActive方法并且存在布局约束
    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;

    //第一个view属性不是size,并且没有第二个属性;
    //设置第二个布局对象为第一个view属性的view的父view
    //设置第二个布局属性为第一个布局属性
    // 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;
    //设置key
    layoutConstraint.mas_key = self.mas_key;
    
    //第二个view属性里的view有值
    if (self.secondViewAttribute.view) {
        //获取两个view的共同superview
        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为共同superview
        self.installedView = closestCommonSuperview;
    } else if (self.firstViewAttribute.isSizeAttribute) {
        //如果第一个view属性是尺寸属性
        //设置已安装的view为第一个view属性里的view
        self.installedView = self.firstViewAttribute.view;
    } else {
        //设置已安装的view为第一个view属性里的view的superview
        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];
    }
}

源码至此分析完毕,期间可能忽略了一些内容,大家可以照着代码自行消化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值