iOS Developer

Masonry源码分析

2017-04-05  本文已影响173人  dKingbin

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

上一篇下一篇

猜你喜欢

热点阅读