美文网首页iOS Developer
Masonry源码分析

Masonry源码分析

作者: dKingbin | 来源:发表于2017-04-05 18:18 被阅读173次

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

    1、AutoLayout的约束设置
    [NASLayoutConstraint constraintWithItem:
                                  attribute:
                                  relatedBy:
                                     toItem:
                                  attribute:
                                 multiplier:
                                   constant:];
    

    加上一个priority,可以看到如果要设置view的约束,总共需要八个参数。
    在Masonry中,这八个参数都用一个类MASConstraint存储起来。
    MASConstraint有两个子类:
    1、MASViewConstraint --- 针对单个约束的设置;
    2、MASCompositeConstraint -- 针对一组约束的设置;
    通常情况下我们只用到MASViewConstraint,因此对于MASCompositeConstraint 就不再详细讨论了。

    2、一些基础的知识讲解

    在AutoLayout中,我们知道只有当两个view之间存在公共祖先,才能设置它们之间的约束关系。
    而view有一个参数superview,即指向父类的指针 -- 因此这个问题就可以转化为求两个链表中的相交点

    2.1、正常情况下的求两个链表中的相交点

    这个问题就很简单了-- 遍历两个链表,求出它们的各自长度L1/L2,然后长的先走
    || L1-L2 ||步,最后再一起走下去,判断是否相等即可。
    显然,算法的复杂度为O(n)。

    2.2、Masonry中的求两个链表中的相交点
    - (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
        MAS_VIEW *closestCommonSuperview = nil;
        MAS_VIEW *secondViewSuperview = view;
        while (!closestCommonSuperview && secondViewSuperview) {
            MAS_VIEW *firstViewSuperview = self;
            while (!closestCommonSuperview && firstViewSuperview) {
                if (secondViewSuperview == firstViewSuperview) {
                    closestCommonSuperview = secondViewSuperview;
                }
                firstViewSuperview = firstViewSuperview.superview;
            }
            secondViewSuperview = secondViewSuperview.superview;
        }
        return closestCommonSuperview;
    }
    

    显然,Masonry并没有按照常规的思路,而是直接两两比较,算法复杂度为O(n^2),为什么Masonry没有采取#2.1的做法,个人看法是:我们在设置约束的时候,通常两个view之间的父类都是靠得非常近的,因此反而#2.2的做法比#2.1的高效。

    2.3、关于优先级
        typedef UILayoutPriority MASLayoutPriority;
        static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
        static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
        static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
        static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
        static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
    

    无论是在AutoLayout中,还是Masonry中,默认的优先级都是UILayoutPriorityRequired,即最高优先级。
    这个在MASViewConstraint初始化中即有体现:

    - (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
        self = [super init];
        if (!self) return nil;
        _firstViewAttribute = firstViewAttribute;
        self.layoutPriority = MASLayoutPriorityRequired;
        self.layoutMultiplier = 1;
        return self;
    }
    
    3、Masonry三个布局方法的源码分析
    - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
        self.translatesAutoresizingMaskIntoConstraints = NO;
        MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
        block(constraintMaker);               //#1
        return [constraintMaker install];
    }
    
    - (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block {
        self.translatesAutoresizingMaskIntoConstraints = NO;
        MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
        constraintMaker.updateExisting = YES;   //#2
        block(constraintMaker);
        return [constraintMaker install];
    }
    
    - (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
        self.translatesAutoresizingMaskIntoConstraints = NO;
        MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
        constraintMaker.removeExisting = YES;    //#3
        block(constraintMaker);
        return [constraintMaker install];        //#4
    }
    
    mas_makeConstraints
    mas_updateConstraints
    mas_remakeConstraints
    

    上面三个方法的实现基本是一样的,区别于细节决定了它们的功能。

    3.1、MASConstraintMaker包装类

    代码#1处就是block的链式调用,这也是Masonry里面比较有趣的东西,各种make。

    MASConstraintMaker是一个包装类,它存储了view设置的各种约束。
    然后在#4处间接跳到了MASViewConstraint的install方法,这是Masonry最关键的方法了。

    3.2、MASViewConstraint中的install方法
    - (void)install {
        if (self.hasBeenInstalled) {       //#5
            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) {   //#6
            secondLayoutItem = self.firstViewAttribute.view.superview;
            secondLayoutAttribute = firstLayoutAttribute;
        }
        
        MASLayoutConstraint *layoutConstraint                                //#7
            = [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;                       //#8
        
        if (self.secondViewAttribute.view) {                           //#9  
            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) {     //#10
            existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
        }
        if (existingConstraint) {
            // just update the constant
            existingConstraint.constant = layoutConstraint.constant;   //#11
            self.layoutConstraint = existingConstraint;
        } else {
            [self.installedView addConstraint:layoutConstraint];  //#12
            self.layoutConstraint = layoutConstraint;
            [firstLayoutItem.mas_installedConstraints addObject:self];
        }
    }
    

    代码#5处表示的是该约束是否处于激活的状态,可以从#13和#14中看出,其实就是表示NSLayoutConstraint的isActive属性,一般情况下新建的约束,active属性默认是NO。

    - (BOOL)supportsActiveProperty {
        return [self.layoutConstraint respondsToSelector:@selector(isActive)];
    }
    
    - (BOOL)isActive {
        BOOL active = YES;
        if ([self supportsActiveProperty]) {
            active = [self.layoutConstraint isActive];  //#14
        }
        return active;
    }
    
    - (BOOL)hasBeenInstalled {
        return (self.layoutConstraint != nil) && [self isActive];   //#13
    }
    

    代码#6处表示如果设置的是长度或者宽度,则secondLayoutItem设置为父类。
    在判断两个MASViewAttribute是否相等的时候,重载了isEqual和hash方法,isEqual主要是==判断的时候使用,而hash则是在NSDictionary/NSSet等需要hash算法的时候调用。

    - (BOOL)isSizeAttribute {
        return self.layoutAttribute == NSLayoutAttributeWidth
            || self.layoutAttribute == NSLayoutAttributeHeight;  //#14
    }
    
    - (BOOL)isEqual:(MASViewAttribute *)viewAttribute {
        if ([viewAttribute isKindOfClass:self.class]) { //#15
            return self.view == viewAttribute.view
                && self.layoutAttribute == viewAttribute.layoutAttribute;
        }
        return [super isEqual:viewAttribute];
    }
    
    - (NSUInteger)hash {  //#16
        return MAS_NSUINTROTATE([self.view hash], MAS_NSUINT_BIT / 2) ^ self.layoutAttribute;
    }
    

    代码#7就是直接调用AutoLayout的初始化约束方法;
    代码#9处找到要设置约束的view;
    代码#10处即是mas_updateConstraints和其余两个方法的区别,在#11处,如果mas_updateConstraints,即会更新原有的约束;

    但是要格外注意的是layoutConstraintSimilarTo这个方法,如果我们在调用mas_updateConstraints中,要update的属性firstItem/secondItem/firstAttribute/secondAttribute/relation/multiplier/priority这7个属性不尽相同,那么就会跳到#12,添加了一个重复的约束,AutoLayout一般情况下都会报错。因此要谨记:

    mas_updateConstraints只是更新约束的constant值,其他不变,也不应该变化,如果变了,那么请不要用mas_updateConstraints,而应该考虑mas_remakeConstraints。
    如果要更新的约束不存在,那么mas_updateConstraints就会退化成mas_makeConstraints。

    - (MASLayoutConstraint *)layoutConstraintSimilarTo:(MASLayoutConstraint *)layoutConstraint {
        // check if any constraints are the same apart from the only mutable property constant
    
        // go through constraints in reverse as we do not want to match auto-resizing or interface builder constraints
        // and they are likely to be added first.
        for (NSLayoutConstraint *existingConstraint in self.installedView.constraints.reverseObjectEnumerator) {
            if (![existingConstraint isKindOfClass:MASLayoutConstraint.class]) continue;
            if (existingConstraint.firstItem != layoutConstraint.firstItem) continue;
            if (existingConstraint.secondItem != layoutConstraint.secondItem) continue;
            if (existingConstraint.firstAttribute != layoutConstraint.firstAttribute) continue;
            if (existingConstraint.secondAttribute != layoutConstraint.secondAttribute) continue;
            if (existingConstraint.relation != layoutConstraint.relation) continue;
            if (existingConstraint.multiplier != layoutConstraint.multiplier) continue;
            if (existingConstraint.priority != layoutConstraint.priority) continue;
    
            return (id)existingConstraint;
        }
        return nil;
    }
    
    3.3、关于mas_remakeConstraints

    mas_remakeConstraints会在MASConstraintMaker调用install的时候移除原来所有的约束,然后下面的步骤和mas_makeConstraints是一样的。就不在详说了。

    - (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;
    }
    
    3.4、关于mas_makeConstraints

    mas_makeConstraints的源码在上面分析install的时候已经分析清楚了。但是有一点要注意的是:尽量不要对同一个view连续使用mas_makeConstraints进行约束设置,虽然对于没有添加过的约束,也是会设置成功的,但是对于同一个约束,那肯定是会报错的。

    END

    相关文章

      网友评论

        本文标题:Masonry源码分析

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