美文网首页
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源码分析

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