iOS 开发每天分享优质文章

Masonry代码解读

2019-01-14  本文已影响89人  mkb2

一.前言

Masonry是非常有名的布局框架,今天我们就分析它的具体实现。通读了一边源码,写的非常的好,有很多值得我们学习的地方。

二.前期准备

Masonry之所以非常让人着迷,得益于简单的api和链式编程,提高约束代码的可读性。

例如:

[greenView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.width.mas_greaterThanOrEqualTo(200);
        make.width.mas_lessThanOrEqualTo(340);
        make.height.mas_equalTo(40);
        make.left.mas_greaterThanOrEqualTo(view1);
        make.top.mas_equalTo(120);
    }];

要阅读Masonry源码,首先要了解链式编程组合模式,其中组合模式格外重要,因为Masonry中使用了该设计模式。

三.整体结构图 & 各类提供的作用

Masonry架构图

在组合模式中,属于叶子节点!!!不可以添加子节点。Masonry中如果想让叶子节点变为树枝节点,会将两个叶子节点放到一个数组中,然后创建一个树枝节点,然后用树枝节点替换叶子节点的位置。

// 布局公式:view1.attr1 = view2.attr2 * multiplier + constant 

// view1的各个属性参数
@property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute;
// view2的各个属性参数,secondViewAttribute可以是NSValue,UIView,MASViewAttribute,提供了较多的方式,后续会分析
@property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute;
// 某个view已安装的约束
+ (NSArray *)installedConstraintsForView:(MAS_VIEW *)view;

 // .m文件实现方法
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;
// 非常重要的方法
- (void)install;
关系图&委托关系

四.Masonsy 如何工作的

下面通过代码来看看Masonry是如何运作的,代码如下:

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.left.bottom.mas_equalTo(20);
        make.width.mas_equalTo(40)�;
 }];
4.1创建make对象,执行block
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    // a.关闭转换AutoresizingMask至Constraints的属性
    self.translatesAutoresizingMaskIntoConstraints = NO;
    // b.创建make,用weak保存self(设置约束的view)
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    // c.执行block,make.top.left.bottom.mas_equalTo(20); && make.width.mas_equalTo(40)�;
    block(constraintMaker);
    // d.执行install操作
    return [constraintMaker install];
}
4.2 block(constraintMaker);

这里执行make.top语句,即:

// make.x 直接返回MASConstraint对象
- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

#pragma mark - MASConstraintDelegate 实现的代理方法

- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
   // 如果将viewConstraint替换为compositionConstaint,调用此方法
    NSUInteger index = [self.constraints indexOfObject:constraint];
    NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
    [self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}

// 该方法是将树枝节点或者是叶子节点直接存放到make.constraints数组中
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    // 如果是make.x 的时候,constraint = nil,就是创建一个MASViewConstraint
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        // 将MASViewConstraint 变成MASCompositeConstraint使用调用。
        // make.top.left的时候会执行到这里 ,make.top.left.bottom也会执行到这里。实际上就是MASCompositeConstraint存放了三个MASViewConstraint。
        // MASCompositeConstraint放置在make.constraints数组中。典型的组合模式
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    // 当前make.top会执行这里newConstraint = topConstraint,将topConstraint.delegate = self(make)
    if (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

这里执行make.top.left语句,刚才通过make.top返回了MASViewConstraint对象,现在看看MASViewConstraint.left的函数实现

// 重写父类方法
#pragma mark - attribute chaining

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
    // 此时self = newConstraint,layoutAttribute = left,带着constraint:self参数调用了代理方法,此时self.delegate = make
    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}

此时会调用make的方法,返回CompositionConstraint对象

// make.m 文件
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    // constraint = topConstraint
    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;
    }
    //下面代码不会执行
    if (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

执行make.top.left.bottom代码
会执行MASCompositionConstraint.bottom方法。
该代码的含义,就是创建了bottomViewConstraint,然后被添加到了MASCompositionConstraint中。
现在MASCompositionConstraint有三个viewConstraint了。分别是top,left.bottom

// MASCompositionConstraint.m 文件

#pragma mark - attribute chaining

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    [self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
    return self;
}
#pragma mark - MASConstraintDelegate

- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
    NSUInteger index = [self.childConstraints indexOfObject:constraint];
    NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
    [self.childConstraints replaceObjectAtIndex:index withObject:replacementConstraint];
}

- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    id<MASConstraintDelegate> strongDelegate = self.delegate;
    MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
    newConstraint.delegate = self;
    [self.childConstraints addObject:newConstraint];
    return newConstraint;
}
// make.m 文件
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    // 此时constraint = compositionConstraint,所以不会执行到这里
    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 = compositionConstraint,所以不会执行到这里
    if (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
    // 执行这里,就是创建一个viewConstraint,然后返回viewConstraint
    return newConstraint;
}

make.top.right.bottom.mas_equalTo(20)的逻辑

make.top.right.bottom前面返回了viewConstraint对象,然后执行mas_equal(20)

// 抽象类MASConstraint.m中
#pragma mark - NSLayoutRelation proxies

- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

//  具体实现类中 MASViewConstraint.m文件

// 执行mas_equalTo,或者mas_greaterThanTo等方法,可以传递多种类型对象,例如某个view.mas_x,也可以是一个数组。
// 如果是数组,就会进入`[attribute isKindOfClass:NSArray.class]`内部,
// 然后将多个sectionAttribution创建成compositionConstraint存储起来。equalToWithRelation只是存储或替换数据,
// 不会立刻执行,要通过install来执行。
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        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 {
            NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}

接着要执行make.width.mas_equalTo(40)�;,逻辑和上边的一致。
执行完毕后,要知道make.constraints中有1个compositionContraint,一个viewConstraint.compositionContraint放置了三个viewConstraint

4.4 [make install]

此时block函数全部执行完毕,现在接着执行[make install]函数

// make.m 文件
- (NSArray *)install {
  // 如果是re_make方法,就会执行这个
    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;
        // 具体是约束执行install方法
        [constraint install];
    }
    [self.constraints removeAllObjects];
    return constraints;
}

进入constraint文件查看具体install方法

- (void)install {
    if (self.hasBeenInstalled) {
        return;
    }
    
    if ([self supportsActiveProperty] && self.layoutConstraint) {
        self.layoutConstraint.active = YES;
        [self.firstViewAttribute.view.mas_installedConstraints addObject:self];
        return;
    }
    
    // view1.attr1 = view2.attr2 * multiplier + constant
    
    MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
    NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
    MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
    NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;

    // 对齐两个view必须有 secondViewAttribute
    // 如果没有secondViewAttribute,我们假设是superview
    // eg make.left.equalTo(@10)
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }
    
    // 仅仅是创建layoutConstraint,还未应用
    MASLayoutConstraint *layoutConstraint
        = [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;
    // 确定该约束应该作用到那个view上
    // 如果有secondViewAttribute.view 有可能调用的时候是make.left.equalTo(�otherView)
    if (self.secondViewAttribute.view) {
        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) {
        // 如果是make.width.mas_equalTo(4);那么直接将约束安装到firstViewAttribute.view上
        self.installedView = self.firstViewAttribute.view;
    } else {
        self.installedView = self.firstViewAttribute.view.superview;
    }


    MASLayoutConstraint *existingConstraint = nil;
    // 仅仅是mas_update某个属性
    if (self.updateExisting) {
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    }
    if (existingConstraint) {
        // just update the constant
        existingConstraint.constant = layoutConstraint.constant;
        self.layoutConstraint = existingConstraint;
    } else {
        // 过去view上没有该约束,直接安装即可,并且将该约束添加到firstLayoutItem的已安装约束数组中,将来如果使用了mas_remake好去移除,mas_update好去寻找约束然后更新
        [self.installedView addConstraint:layoutConstraint];
        self.layoutConstraint = layoutConstraint;
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}

至此设置view约束的代码就全部执行完毕。

五.masonry提供的其他功能

1.make提供了某个属性的设置,例如,left,right,top,bottom.也可以使用组合的方式来设置约束.

@property (nonatomic, strong, readonly) MASConstraint *(^attributes)(MASAttribute attrs);

   [view1 mas_makeConstraints:^(MASConstraintMaker *make) {
        make.attributes(MASAttributeTop | MASAttributeLeft).mas_equalTo(133);
        make.attributes(MASAttributeBottom | MASAttributeRight).mas_equalTo(-133);
    }];

2.查看当前view有哪些约束

+ (NSArray *)installedConstraintsForView:(MAS_VIEW *)view;

六.设计亮点

6.1 自定义添加了约束的优先级

在已有的优先级基础上,在添加新的优先级。
为了防止出错,直接使用过去的赋值。

    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;
6.2.firstViewAttribute && secondViewAttribute 相对灵活

原生的布局的计算公式为,所以需要两个view。但是masonry假设只给定一个view(即firstViewAttribute)也行,另一个view根据某些给定的参数来确定。如果有了第二个view(secondViewAttribute)便可以直接布局了。
但是这里的secondViewAttribute不一定是view,可以是其他的,masonry会根据实际情况来确定,然后布局的。
secondViewAttribute 可以是NSValue,MAS_VIEW,MASViewAttribute,非常的灵活。

6.3.用于判断view所安装的约束,采用了option来修饰MASAttribute属性
typedef NS_OPTIONS(NSInteger, MASAttribute) {
    MASAttributeLeft = 1 << NSLayoutAttributeLeft,
    MASAttributeRight = 1 << NSLayoutAttributeRight,
    MASAttributeTop = 1 << NSLayoutAttributeTop,
    MASAttributeBottom = 1 << NSLayoutAttributeBottom,
    MASAttributeLeading = 1 << NSLayoutAttributeLeading,
    MASAttributeTrailing = 1 << NSLayoutAttributeTrailing,
    MASAttributeWidth = 1 << NSLayoutAttributeWidth,
    MASAttributeHeight = 1 << NSLayoutAttributeHeight,
    MASAttributeCenterX = 1 << NSLayoutAttributeCenterX,
    MASAttributeCenterY = 1 << NSLayoutAttributeCenterY,
    MASAttributeBaseline = 1 << NSLayoutAttributeBaseline,

    MASAttributeFirstBaseline = 1 << NSLayoutAttributeFirstBaseline,
    MASAttributeLastBaseline = 1 << NSLayoutAttributeLastBaseline,
    
#if TARGET_OS_IPHONE || TARGET_OS_TV
    
    MASAttributeLeftMargin = 1 << NSLayoutAttributeLeftMargin,
    MASAttributeRightMargin = 1 << NSLayoutAttributeRightMargin,
    MASAttributeTopMargin = 1 << NSLayoutAttributeTopMargin,
    MASAttributeBottomMargin = 1 << NSLayoutAttributeBottomMargin,
    MASAttributeLeadingMargin = 1 << NSLayoutAttributeLeadingMargin,
    MASAttributeTrailingMargin = 1 << NSLayoutAttributeTrailingMargin,
    MASAttributeCenterXWithinMargins = 1 << NSLayoutAttributeCenterXWithinMargins,
    MASAttributeCenterYWithinMargins = 1 << NSLayoutAttributeCenterYWithinMargins,

#endif
    
};


使用处
    NSAssert((attrs & anyAttribute) != 0, @"You didn't pass any attribute to make.attributes(...)");
    
    NSMutableArray *attributes = [NSMutableArray array];
    
    if (attrs & MASAttributeLeft) [attributes addObject:self.view.mas_left];
    if (attrs & MASAttributeRight) [attributes addObject:self.view.mas_right];
    if (attrs & MASAttributeTop) [attributes addObject:self.view.mas_top];
    if (attrs & MASAttributeBottom) [attributes addObject:self.view.mas_bottom];
    if (attrs & MASAttributeLeading) [attributes addObject:self.view.mas_leading];
    if (attrs & MASAttributeTrailing) [attributes addObject:self.view.mas_trailing];
    if (attrs & MASAttributeWidth) [attributes addObject:self.view.mas_width];
    if (attrs & MASAttributeHeight) [attributes addObject:self.view.mas_height];
    if (attrs & MASAttributeCenterX) [attributes addObject:self.view.mas_centerX];
    if (attrs & MASAttributeCenterY) [attributes addObject:self.view.mas_centerY];
    if (attrs & MASAttributeBaseline) [attributes addObject:self.view.mas_baseline];
    if (attrs & MASAttributeFirstBaseline) [attributes addObject:self.view.mas_firstBaseline];
    if (attrs & MASAttributeLastBaseline) [attributes addObject:self.view.mas_lastBaseline];
    
#if TARGET_OS_IPHONE || TARGET_OS_TV
    
    if (attrs & MASAttributeLeftMargin) [attributes addObject:self.view.mas_leftMargin];
    if (attrs & MASAttributeRightMargin) [attributes addObject:self.view.mas_rightMargin];
    if (attrs & MASAttributeTopMargin) [attributes addObject:self.view.mas_topMargin];
    if (attrs & MASAttributeBottomMargin) [attributes addObject:self.view.mas_bottomMargin];
    if (attrs & MASAttributeLeadingMargin) [attributes addObject:self.view.mas_leadingMargin];
    if (attrs & MASAttributeTrailingMargin) [attributes addObject:self.view.mas_trailingMargin];
    if (attrs & MASAttributeCenterXWithinMargins) [attributes addObject:self.view.mas_centerXWithinMargins];
    if (attrs & MASAttributeCenterYWithinMargins) [attributes addObject:self.view.mas_centerYWithinMargins];
    
#endif
6.4.通过设置NSValue,自动判断类型&为属性设置数据
#pragma mark - NSLayoutConstraint constant setter

- (void)setLayoutConstantWithValue:(NSValue *)value {
    if ([value isKindOfClass:NSNumber.class]) {
        self.offset = [(NSNumber *)value doubleValue];
    } else if (strcmp(value.objCType, @encode(CGPoint)) == 0) {
        CGPoint point;
        [value getValue:&point];
        self.centerOffset = point;
    } else if (strcmp(value.objCType, @encode(CGSize)) == 0) {
        CGSize size;
        [value getValue:&size];
        self.sizeOffset = size;
    } else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) {
        MASEdgeInsets insets;
        [value getValue:&insets];
        self.insets = insets;
    } else {
        NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value);
    }
}
上一篇下一篇

猜你喜欢

热点阅读