美文网首页
Masonry源码分析

Masonry源码分析

作者: 宋鸿康iOS | 来源:发表于2018-01-18 16:35 被阅读296次

Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁,也是函数式编程的典范。废话少说,下面我们来分析Masonry具体源码。

在分析Masonry源码前,我们来回顾下系统的NSLayoutConstraint布局。用过的同学都会认为很蛋疼,代码量很大,重复性的代码很浪费时间,废话少说,直接上代码。

NSLayoutConstraint *constraint1 = [NSLayoutConstraint 
                                   constraintWithItem:subView                       
                                   attribute:NSLayoutAttributeTop 
                                   relatedBy:NSLayoutRelationEqual
                                   toItem:superView
                                   attribute:NSLayoutAttributeTop 
                                   multiplier:1.0 
                                   constant:40];

上面约束代码用表达式描述: subview.top = superView.top *1 +40;
用文字描述:subView的某个属性(attr1)等于superView的某个属性(attr2)的值的多少倍(multiplier)加上某个常量(constant

Masonry写这个约束:

make.top.equalTo(@40);
or
make.top.equalT0(superView.mas_top).offset(40);

都是表达的subview.top = superView.top *1 +40;
显而易见Masonry比NSLayoutConstraint写法方便许多。而且还是链式调用,更加的方便,废话少说,分析

    UILabel *testLabel = [[UILabel alloc] init];
    testLabel1.text = @"songhongkang";
    [self.view addSubview:testLabel];
    [testLabel1 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(@100);
        make.left.equalTo(@50);
        make.right.equalTo(@-50);
    }];

View+MASAdditions 分类中有些语法先解释下

#if TARGET_OS_IPHONE || TARGET_OS_TV

#endif

如果if中的条件是YES,if里面的代码将会被编译,如果if中的条件是NO,if里面的代码将不会被编译

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    // 如果要使用autolayout  'translatesAutoresizingMaskIntoConstraints' 属性 必须是No
    // 默认是YES
    self.translatesAutoresizingMaskIntoConstraints = NO;
    // 创建 MASConstraintMaker 对象, 把当前视图传给MASConstraintMaker 保存
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    //把constraintMaker传给block
    block(constraintMaker);
    // 安装约束
    return [constraintMaker install];
}

make.top 调用 MASConstraintMaker 的 top get方法:

- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
// 分析
//make.top.equalTo(@100);
//make.left.equalTo(@50);
//make.right.equalTo(@-50);
//constraint参数为nil

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    // 实例化一个 MASViewAttribute 对象.
    // self.view 需要添加约束的视图(view) 传过去
    // layoutAttribute 约束的属性 (NSLayoutAttributeLeft、NSLayoutAttributeTop、NSLayoutAttributeRight) 等
    // viewAttributed对象中的属性已经被赋值了
    // view 、 item 、layoutAttribute 都已经被赋值
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    // 实例化一个 MASViewConstraint 对象, 并且给这个对象的属性firstViewAttribute 赋值, 把viewAttribute赋值给firstViewAttribute属性
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    //  constraint 为 nil ,不执行这个if
    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;
    }
    //  constraint 为 nil ,执行这个if
    if (!constraint) {
        // 给MASViewConstraint对象设置代理,为了是链式调用 eg:
        // make.left.equal(@10);
         newConstraint.delegate = self;
        // 把所有的约束对象放到当前对象的constraints数组中
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

make.top.equalTo(@100); 调用 MASConstraint 的 equalTo get方法:

- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { MASMethodNotImplemented(); }
都要在子类中实现

#pragma mark - NSLayoutRelation proxy

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        // eg: make.left.equalTo(@10);
        // attribute = 10;  relation = NSLayoutRelationEqual
        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.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 {
            // #define NSAssert(condition, desc)
            //condition是条件表达式,值为YES或NO;desc为异常描述,通常为NSString。当conditon为YES时程序继续运行,为NO时,则抛出带有desc描述的异常信息。NSAssert()可以出现在程序的任何一个位置。
            // self.hasLayoutRelation  表示:
            // self.layoutRelation == relation  表示:
            NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
            // eg: make.left.equalTo(@10);
            // attribute = 10;  relation = NSLayoutRelationEqual
            // 再去调用set方法, 把属性 self.hasLayoutRelation  设置YES
            self.layoutRelation = relation;
            // set方法 根据attribute类型有不同的赋值,具体可以看看
            // 通过一系列赋值 对象中的属性 layoutConstant 已经有值了。
            // eg: make.left.equalTo(@10);
            //layoutConstant = 10

            // eg: make.left.equalTo(self.view).offset(10);
            //layoutConstant = self.view

            self.secondViewAttribute = attribute;
            return self;
        }
    };
}

secondViewAttribute set方法可以关注下,感觉写法比较6

- (void)setSecondViewAttribute:(id)secondViewAttribute {
    // make.left.equalTo(@10)
    // 数字
    // uiview
    // MASViewAttribute
    //
    if ([secondViewAttribute isKindOfClass:NSValue.class]) {
        [self setLayoutConstantWithValue:secondViewAttribute];
    } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
        // make.left.equalTo(self.view).offset(100);
        // secondViewAttribute 是 self.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);
    }
}

到目前所有的约束都放到MASConstraintMaker对象的constraints属性中了
return [constraintMaker install];

- (NSArray *)install {
    
     //- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
     //- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;
     // 现在分析 的是mas_makeConstraints方法... 所以self.removeExisting是NO
    if (self.removeExisting) {
        
        NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
        for (MASConstraint *constraint in installedConstraints) {
            [constraint uninstall];
        }
    }
    // eg:
    // make.top.equalTo(@100);
    // make.left.equalTo(@50);
    // make.right.equalTo(@-50);
    // constraints 数组现在存放都是 MASViewConstraint的对象
    NSArray *constraints = self.constraints.copy;
    // 把约束对象遍历出来,并且把updateExisting 更新约束赋值
    //- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
    // 现在调用的是此方法,所以self.updateExisting 为NO
    // 安装约束,具体实现看 install 方法的注释
    for (MASConstraint *constraint in constraints) {
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
    [self.constraints removeAllObjects];
    return constraints;
}

主要到的一点,看看约束怎么安装在公共的视图上面
安装约束在MASViewConstrain类中

- (void)install {
    // 约束已经安装,返回return, 此方法的下面的代码不会被执行
    //
    if (self.hasBeenInstalled) {
        return;
    }
    // 此处分析是约束条是
    //make.top.equalTo(@100);
    //make.left.equalTo(@50);
    //make.right.equalTo(@-50);
    // layoutConstraint 始终没有被初始化过, 这个if条件不会被执行
    // 代码具体是什么意思。后面分析
    
    if ([self supportsActiveProperty] && self.layoutConstraint) {
        self.layoutConstraint.active = YES;
        [self.firstViewAttribute.view.mas_installedConstraints addObject:self];
        return;
    }
    
    // firstLayoutItem 就是Label
    MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
    //firstLayoutAttribute, NSLayoutAttributeLeft
    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) self.secondViewAttribute 为 nil
    // eg.make.left.equatTo(self.view.mas_top) self.secondViewAttribute 为self.view
    // self.firstViewAttribute.isSizeAttribute 为YES , 表示这个约束是
    //NSLayoutAttributeWidth 。 NSLayoutAttributeHeight
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        
        // self.firstViewAttribute.view 父视图赋值给 secondLayoutItem
        secondLayoutItem = self.firstViewAttribute.view.superview;
        // firstLayoutAttribute 赋值给 secondLayoutAttribute
        secondLayoutAttribute = firstLayoutAttribute;
    }
    // 生成一个约束对象 NSLayoutConstraint
    MASLayoutConstraint *layoutConstraint
        = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                        attribute:firstLayoutAttribute
                                        relatedBy:self.layoutRelation
                                           toItem:secondLayoutItem
                                        attribute:secondLayoutAttribute
                                       multiplier:self.layoutMultiplier
                                         constant:self.layoutConstant];
    // 默认的约束优先级是 1000
    layoutConstraint.priority = self.layoutPriority;
    // mae_key 这个参数为了方便调试约束
    layoutConstraint.mas_key = self.mas_key;
    // 如果seconndViewAttribute.view 有值
    // eg:make.left.equalTo(self.view);
    if (self.secondViewAttribute.view) {
        
        // self.secondViewAttribute.view = self.view
        // self.firstViewAttribute.view  = label;
        // 找到两个视图的最近的公共视图 ,可以看我的注释,这个方法在View+MASAdditions类中
        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) {
        // 如果约束是 NSLayoutAttributeWidth、  NSLayoutAttributeHeight
        self.installedView = self.firstViewAttribute.view;
    } else {
        // eg:make.left.eqaulTo(@100);
        self.installedView = self.firstViewAttribute.view.superview;
    }
    MASLayoutConstraint *existingConstraint = nil;
    //- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
    // 现在调用的是此方法,所以self.updateExisting 为NO
    if (self.updateExisting) {
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    }
    
    //- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
    // 现在调用的是此方法,所以existingConstraint 为NO
    if (existingConstraint) {
        // just update the constant
        existingConstraint.constant = layoutConstraint.constant;
        self.layoutConstraint = existingConstraint;
    } else {
        // - (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block {
        // 调用此方法,就会执行下面代码
        [self.installedView addConstraint:layoutConstraint];
        // 把变量赋值给全局变量 , 不知道有什么用
        self.layoutConstraint = layoutConstraint;
        // 把MASViewConstraint 约束保存起来
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}

经常我们需要给某一个UIView添加约束,那么它的约束应该添加到那个视图上了?
其实单纯的说添加给他们两个哪一个控件都是不够准确的,当然在一些情况下也有可能是两个Item中的一个。其实应该是加在离他们最近的公共视图上。 怎么找最近的公共视图,(这里就的思想有点像求最小公倍数)代码如下:

- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
    MAS_VIEW *closestCommonSuperview = nil; // 保存公共的父视图
    MAS_VIEW *secondViewSuperview = view;
   
    while (!closestCommonSuperview && secondViewSuperview) {
        MAS_VIEW *firstViewSuperview = self;
        while (!closestCommonSuperview && firstViewSuperview) { //遍历secondViewSuperview的父视图
            if (secondViewSuperview == firstViewSuperview) { // 如果有相同的
                closestCommonSuperview = secondViewSuperview;
            }
            firstViewSuperview = firstViewSuperview.superview;
        }
        secondViewSuperview = secondViewSuperview.superview;
    }
    // 返回最近的父视图
    return closestCommonSuperview;
}

记录下,有空再来更新下。 2018年01月18日17:53:43

相关文章

  • Masonry框架源码分析

    Masonry框架源码分析 相信大多数iOS开发者对Masonry框架并不陌生 , 本文是笔者通读Masonry的...

  • Masonry 源码分析

    Masonry 提供了简单方便的api ,供我们完成项目中的自动布局业务。 从使用的 api 开始讲 调用mas_...

  • Masonry源码分析

    Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使...

  • Masonry源码分析

    Masonry源码其实非常简单,就是对AutoLayout的简单封装,但是使用中有几处要注意的地方,所以现在就Ma...

  • Masonry源码分析

    iOS 源代码分析 --- Masonry Masonry 是 Objective-C 中用于自动布局的第三方框架...

  • Masonry 介绍 2018-01-29

    介绍 Masonry 源码:https://github.com/Masonry/Masonry Masonry是...

  • 关于Masonry小记

    Masonry 源码:https://github.com/Masonry/Masonry Masonry是一个轻...

  • Masonry的用法

    Masonry 源码:https://github.com/Masonry/Masonry; 看一下Masonry...

  • Masonry源码分析(下)

    前言 在上一篇-Masonry源码分析(上)文章中介绍了Masonry的文件结构、大致讲了一下类中的方法,希望大家...

  • Masonry学习报告

    Masonry 源码:https://github.com/Masonry/Masonry 如果是使用cocoa ...

网友评论

      本文标题:Masonry源码分析

      本文链接:https://www.haomeiwen.com/subject/hderbxtx.html