[iOS开发]Masonry源码学习

39 篇文章 0 订阅

苹果提供的自动布局(Auto Layout)能够对视图进行灵活有效的布局。但是,使用原生的自动布局相关的语法创建约束的过程是非常冗长的,可读性也比较差。使用原生的自动布局语法,对于如此简单的一个布局,也是非常冗长的。如果使用 VFL(Visual Format Language)可以有效减少冗余,但是其 ASCII 类型语法使得编译器无法做类型检查,存在一定的安全隐患。
Masonry 的目标其实就是为了解决原生自动布局语法冗长的问题

Masonry架构

基本组成

在这里插入图片描述
Masonry 主要方法由上述例子就可一窥全貌。Masonry主要通过对 UIViewNSView)、NSArrayUIViewController 进行分类扩展,从而提供自动布局的构建方法。

实现流程

先看一个一般的使用Masory

[myView mas_makeConstraints:^(MASConstraintMaker *make) {
	make.width.height.mas_equalTo(@100);
    make.centerX.mas_equalTo(self.view.mas_centerX);
    make.top.mas_equalTo(self.view.mas_top).offset(200);
}];

接下来看整个添加约束的流程:
先调用

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}

先调用UIView的分类MASAdditions中的mas_makeConstraints:方法,在该方法中调用constraintMaker的初始化方法,传给该方法要设置约束的view,生产maker对象,传给block回调出去,然后再调用makerinstall方法。
下面看一下初始化方法:

- (id)initWithView:(MAS_VIEW *)view {
    self = [super init];
    if (!self) return nil;
    
    self.view = view;
    self.constraints = NSMutableArray.new;
    
    return self;
}

initWithView:会记录下要设置约束的当前view,同时创建一个数组用来存储即将使用maker添加的约束。在传给mas_makeConstraints方法的block参数中,使用回调出来的maker进行一一添加约束。下面是使用make.width点语法后的全部内部调用过程:

// MASConstraintMaker
- (MASConstraint *)width {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}

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

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    // 根据 约束属性 和 视图 创建一个约束单元
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    //创建约束,以约束单元作为约束的第一项
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        //replace with composite constraint
        // 如果是在已有约束的基础上再创建的约束,则将它们转换成一个 组合约束,并将原约束替换成该组合约束。
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        // 这里会将原来 make.width 添加的约束 替换成一个 组合约束(宽度约束 + 高度约束)
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        // 返回组合约束
        return compositeConstraint;
    }
    if (!constraint) {// 如果不是在已有约束的基础上再创建约束,则添加约束至列表
        newConstraint.delegate = self;// 注意这一步,会对 make.top.left 这种情形产生关键影响
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

在第二次设置约束时(.height)会进入不同的流程。注意上面提到的newConstraint.delegate设置代理:

//MAConstraint
- (MASConstraint *)height {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}
//MSViewConstraint
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
	//delegate是MASConstraintMaker
    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
// MASConstraintMaker
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {...}

下面看一下.mas_equalTo(@100)的流程。

// MASConstraint
#define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        // attribute 可能是 @100 类似的值,也可能是 view.mas_width等这样的
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}
- (MASConstraint * (^)(id))mas_equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}
// MASViewConstraint
- (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;
        }
    };
}
// 设置约束第二项
- (void)setSecondViewAttribute:(id)secondViewAttribute {
	//判断类型
    if ([secondViewAttribute isKindOfClass:NSValue.class]) {
        [self setLayoutConstantWithValue:secondViewAttribute];
    } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
        _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);
    }
}
// MASConstraint
- (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);
    }
}

// MASViewConstraint
- (void)setOffset:(CGFloat)offset {
    self.layoutConstant = offset;       // 设置约束常量
}
- (void)setSizeOffset:(CGSize)sizeOffset {
    NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
    switch (layoutAttribute) {
        case NSLayoutAttributeWidth:
            self.layoutConstant = sizeOffset.width;
            break;
        case NSLayoutAttributeHeight:
            self.layoutConstant = sizeOffset.height;
            break;
        default:
            break;
    }
}
- (void)setCenterOffset:(CGPoint)centerOffset {
    NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
    switch (layoutAttribute) {
        case NSLayoutAttributeCenterX:
            self.layoutConstant = centerOffset.x;
            break;
        case NSLayoutAttributeCenterY:
            self.layoutConstant = centerOffset.y;
            break;
        default:
            break;
    }
}
- (void)setInsets:(MASEdgeInsets)insets {
    NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
    switch (layoutAttribute) {
        case NSLayoutAttributeLeft:
        case NSLayoutAttributeLeading:
            self.layoutConstant = insets.left;
            break;
        case NSLayoutAttributeTop:
            self.layoutConstant = insets.top;
            break;
        case NSLayoutAttributeBottom:
            self.layoutConstant = -insets.bottom;
            break;
        case NSLayoutAttributeRight:
        case NSLayoutAttributeTrailing:
            self.layoutConstant = -insets.right;
            break;
        default:
            break;
    }
}

再看如果约束对象是一个控件(mas_equalTo(self.view.mas_top)),那么就会走进下面的这段代码:

else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
	_secondViewAttribute = secondViewAttribute;
} 

另外,后面的 offset 方法做了一步额外的操作:

// MASConstraint
- (MASConstraint * (^)(CGFloat))offset {
    return ^id(CGFloat offset){
        self.offset = offset;
        return self;
    };
}
- (void)setOffset:(CGFloat)offset {
    self.layoutConstant = offset;
}

回到前面,block执行结束之后,调用了[constraintMaker install],下面看一下install方法的实现:

- (NSArray *)install {
    // 只有在 mas_remakeConstraints 时,removeExisting 才为 YES
    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) {
        // 设置约束的 updateExisting 属性
        // 只有在 mas_updateConstraints 时,updateExisting 才为 YES
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
    // 清空 constraints 数组缓存
    [self.constraints removeAllObjects];
    return constraints;
}

install 方法内部会对 constraints 列表中的所有约束依次执行各自的 install 方法来添加约束。我们来看一下约束的 install 方法:

// MASCompositeConstraint
- (void)install {
    for (MASConstraint *constraint in self.childConstraints) {
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
}

// MASViewConstraint
- (void)install {
    // 约束是否已被添加
    if (self.hasBeenInstalled) {
        return;
    }
    // 如果约束支持 isActive 方法,且 self.layoutConstraint 有值了
    if ([self supportsActiveProperty] && self.layoutConstraint) {
        if (@available(iOS 8.0, *)) {
            self.layoutConstraint.active = YES;
        } else {
            // Fallback on earlier versions
        }
        [self.firstViewAttribute.view.mas_installedConstraints addObject:self];
        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
    //对齐属性必须具有第二个ViewAttribute。因此我们假设它指的是超视图
    // eg make.left.equalTo(@10)
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }
    // 生成一个 NSLayoutConstraint
    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;
    // 确定约束layoutConstraint 的约束层级(即要被添加到的位置)
    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) {
        self.installedView = self.firstViewAttribute.view;
    } else {
        self.installedView = self.firstViewAttribute.view.superview;
    }

    MASLayoutConstraint *existingConstraint = nil;
    if (self.updateExisting) {
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    }
    if (existingConstraint) {
        // just update the constant
        // 约束存在,则更新constant值
        existingConstraint.constant = layoutConstraint.constant;
        self.layoutConstraint = existingConstraint;
    } else {
        // 约束不存在,则在该位置添加约束
        [self.installedView addConstraint:layoutConstraint];
        self.layoutConstraint = layoutConstraint;
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}

无论是 MASCompositeConstraint 还是 MASViewConstraint,本质上还是调用 MASViewConstraintinstall 方法。该方法根据 MASViewConstraint 的各个属性创建一个原生的约束(NSLayoutConstraint 类型),并在定位约束层级后,将约束添加到相应层级的视图上。

重要类

MASLayoutConstraint

@interface MASLayoutConstraint : NSLayoutConstraint

/**
 *	a key to associate with this constraint
 */
@property (nonatomic, strong) id mas_key;

@end

MASLayoutConstraint 类继承自 NSLayoutConstraint 类。相比其父类,它就多了一个属性 mas_keyMASLayoutConstraint 用来表示布局约束。

MASViewAttribute

@interface MASViewAttribute : NSObject

/**
 *  The view which the reciever relates to. Can be nil if item is not a view.
 *  接收方与之相关的视图。如果项目不是视图,则可以为 nil。
 */
@property (nonatomic, weak, readonly) MAS_VIEW *view;

/**
 *  The item which the reciever relates to.
 *  接收方与之相关的项目。
 */
@property (nonatomic, weak, readonly) id item;

/**
 *  The attribute which the reciever relates to
 *  接收方与之相关的属性
 */
@property (nonatomic, assign, readonly) NSLayoutAttribute layoutAttribute;

MASConstraint

MASConstraint 是一个抽象类,主要为其子类 MASViewConstraintMASCompositeConstraint 声明了一些共有的方法。MASConstraint 为这些共有的方法实现了部分功能,底层的细节实现则由其子类决定。
根据约束方程式的组成,可将这些方法分为以下几类:

  • 属性操作方法(Attribute)
  • 关系操作方法(Relationship)
  • 倍数操作方法(Multiplier)
  • 常量操作方法(Constant)

除此之外,还有优先级操作方法。
属性操作方法:
属性操作方法根据对应的 NSLayoutAttribute 枚举类型创建约束属性项。

- (MASConstraint *)left;
- (MASConstraint *)top;
- (MASConstraint *)right;
- (MASConstraint *)bottom;
- (MASConstraint *)leading;
- (MASConstraint *)trailing;
- (MASConstraint *)width;
- (MASConstraint *)height;
- (MASConstraint *)centerX;
- (MASConstraint *)centerY;
- (MASConstraint *)baseline;

#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)

- (MASConstraint *)firstBaseline;
- (MASConstraint *)lastBaseline;

#endif

#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)

- (MASConstraint *)leftMargin;
- (MASConstraint *)rightMargin;
- (MASConstraint *)topMargin;
- (MASConstraint *)bottomMargin;
- (MASConstraint *)leadingMargin;
- (MASConstraint *)trailingMargin;
- (MASConstraint *)centerXWithinMargins;
- (MASConstraint *)centerYWithinMargins;

#endif

这些操作方法内部都是通过一个抽象方法实现,须由子类具体实现,该方法为:

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute

关系操作方法:
关系操作方法根据 NSLayoutRelation 枚举类型创建约束关系项。

- (MASConstraint * (^)(id attr))mas_equalTo;
- (MASConstraint * (^)(id attr))mas_greaterThanOrEqualTo;
- (MASConstraint * (^)(id attr))mas_lessThanOrEqualTo;

这些操作方法内部都是通过一个抽象方法实现,须由子类具体实现,该方法为:

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation;

倍数操作方法:
两个倍数操作方法都是抽象方法,须由子类具体实现。

- (MASConstraint * (^)(CGFloat multiplier))multipliedBy;
- (MASConstraint * (^)(CGFloat divider))dividedBy;

常量操作方法:
常量操作方法内部各自调用对应的 setter 方法,而这些 setter 方法都是抽象方法,须由子类具体实现。

- (MASConstraint * (^)(MASEdgeInsets insets))insets;
- (MASConstraint * (^)(CGFloat inset))inset;
- (MASConstraint * (^)(CGSize offset))sizeOffset;
- (MASConstraint * (^)(CGPoint offset))centerOffset;
- (MASConstraint * (^)(CGFloat offset))offset;
- (MASConstraint * (^)(NSValue *value))valueOffset;

优先级操作方法:
后三个优先级操作方法根据 NSLayoutPriority 枚举类型设置约束优先级,其内部都是通过调用第一个优先级操作方法实现的,该方法为抽象方法,须子类具体实现。

- (MASConstraint * (^)(MASLayoutPriority priority))priority;
- (MASConstraint * (^)())priorityLow;
- (MASConstraint * (^)())priorityMedium;
- (MASConstraint * (^)())priorityHigh;

MASViewConstraint

MASViewConstraintMASConstraint 的子类,可以称之为 Masonry最重要的类
MASViewConstraint 除了能够完整表示约束方程式之外,还存储了约束的优先级属性。我们来看一下其外部属性和内部属性。

//public
@property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute;
@property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute;
//private
@property (nonatomic, strong, readwrite) MASViewAttribute *secondViewAttribute;
//约束被添加到的位置(视图)
@property (nonatomic, weak) MAS_VIEW *installedView;
// 约束
@property (nonatomic, weak) MASLayoutConstraint *layoutConstraint;
// 关系
@property (nonatomic, assign) NSLayoutRelation layoutRelation;
// 优先级
@property (nonatomic, assign) MASLayoutPriority layoutPriority;
// 倍数
@property (nonatomic, assign) CGFloat layoutMultiplier;
// 常量
@property (nonatomic, assign) CGFloat layoutConstant;
@property (nonatomic, assign) BOOL hasLayoutRelation;
@property (nonatomic, strong) id mas_key;
@property (nonatomic, assign) BOOL useAnimator;

我们再来看一下 MASViewConstraint 实现的父类抽象方法。
首先,属性操作方法所调用的一个抽象方法:

#pragma mark - attribute chaining
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    // 必须是没有设置过布局关系,即 hasLayoutRelation 为 NO
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}

MASViewConstraint 其实将该方法的具体实现交给了它的代理。前面讲工作流程时也有提到过,这里实际上是很巧妙的。
其次,关系操作方法所调用的一个抽象方法:

#pragma mark - NSLayoutRelation proxy
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if ([attribute isKindOfClass:NSArray.class]) {
            // 必须是没有设置过布局关系,即 hasLayoutRelation 为 NO
            NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
            // 如果 attribute 是一组属性,则生成一组约束
            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");
            // 如果 attribute 是单个属性,则设置约束的第二项
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;// 设置约束第二项
            return self;
        }
    };
}

可以看到,针对 attribute 的不同,equalToWithRelation 方法实现了不同的逻辑。
接下来,是倍数操作方法所调用的两个抽象方法:

#pragma mark - NSLayoutConstraint multiplier proxies
- (MASConstraint * (^)(CGFloat))multipliedBy {
    return ^id(CGFloat multiplier) {
        NSAssert(!self.hasBeenInstalled,
                 @"Cannot modify constraint multiplier after it has been installed");
        
        self.layoutMultiplier = multiplier;
        return self;
    };
}
- (MASConstraint * (^)(CGFloat))dividedBy {
    return ^id(CGFloat divider) {
        NSAssert(!self.hasBeenInstalled,
                 @"Cannot modify constraint multiplier after it has been installed");

        self.layoutMultiplier = 1.0/divider;
        return self;
    };
}

可以看到,这两个方法本质上就是修改了 MASViewConstraint 的倍数属性 layoutMultiplier
然后,常量操作方法所调用的几个抽象方法:

#pragma mark - NSLayoutConstraint constant setters
// 只有约束方程式第一项的属性是:
// NSLayoutAttributeLeft、NSLayoutAttributeLeading、
// NSLayoutAttributeTop、NSLayoutAttributeBottom、
// NSLayoutAttributeRight、NSLayoutAttributeTrailing
// 时,方法才会有效设置常量属性
//#define MASEdgeInsets UIEdgeInsets
- (void)setInsets:(MASEdgeInsets)insets {
    NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
    switch (layoutAttribute) {
        case NSLayoutAttributeLeft:
        case NSLayoutAttributeLeading:
            self.layoutConstant = insets.left;
            break;
        case NSLayoutAttributeTop:
            self.layoutConstant = insets.top;
            break;
        case NSLayoutAttributeBottom:
            self.layoutConstant = -insets.bottom;
            break;
        case NSLayoutAttributeRight:
        case NSLayoutAttributeTrailing:
            self.layoutConstant = -insets.right;
            break;
        default:
            break;
    }
}
// setInsets 的特殊情况
- (void)setInset:(CGFloat)inset {
    [self setInsets:(MASEdgeInsets){.top = inset, .left = inset, .bottom = inset, .right = inset}];
}
// 直接设置常量属性
- (void)setOffset:(CGFloat)offset {
    self.layoutConstant = offset;
}
// 只有约束方程式第一项的属性是:
// NSLayoutAttributeWidth、NSLayoutAttributeHeight
// 时,方法才会有效设置常量属性
- (void)setSizeOffset:(CGSize)sizeOffset {
    NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
    switch (layoutAttribute) {
        case NSLayoutAttributeWidth:
            self.layoutConstant = sizeOffset.width;
            break;
        case NSLayoutAttributeHeight:
            self.layoutConstant = sizeOffset.height;
            break;
        default:
            break;
    }
}
// 只有约束方程式第一项的属性是:
// NSLayoutAttributeCenterX、NSLayoutAttributeCenterY
// 时,方法才会有效设置常量属性
- (void)setCenterOffset:(CGPoint)centerOffset {
    NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
    switch (layoutAttribute) {
        case NSLayoutAttributeCenterX:
            self.layoutConstant = centerOffset.x;
            break;
        case NSLayoutAttributeCenterY:
            self.layoutConstant = centerOffset.y;
            break;
        default:
            break;
    }
}

这些 setter 方法会根据 MASViewConstraint 已有的 firstViewAttribute 约束项的约束属性 layoutAttribuet 的类型来设置常量属性。当属性不匹配值,对常量属性的设置并不会生效。
最后,优先级操作方法的一个抽象方法:

#pragma mark - MASLayoutPriority proxy
- (MASConstraint * (^)(MASLayoutPriority))priority {
    return ^id(MASLayoutPriority priority) {
        NSAssert(!self.hasBeenInstalled,
                 @"Cannot modify constraint priority after it has been installed");
        self.layoutPriority = priority;
        return self;
    };
}

该方法内部直接设置了 MASViewConstraint 的优先级属性 layoutPriority

MASCompositeConstraint

MASCompositeConstraint 也是 MASConstraint 的子类。与 MASViewConstraint 只表示一个约束不同,MASCompositeConstraint 可以表示一组约束。

@interface MASCompositeConstraint () <MASConstraintDelegate>

@property (nonatomic, strong) id mas_key;
@property (nonatomic, strong) NSMutableArray *childConstraints;

@end

其中,childConstraints 属性持有了一组约束。我们再来看一下 MASCompositeConstraint 实现的父类抽象方法。
首先,看一下属性操作方法所调用的一个抽象方法。

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

该方法调用了 MASCompositeConstraint 所实现的 MASConstraintDelegate 的一个方法。

- (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;
}

可以看出,该方法内部将通过其代理新创建的普通约束或组合约束添加至 MASCompositeConstraintchildConstraints 数组中,并设置子约束的代理为 MASCompositeConstraint 的代理。
实际,在 Masonry 中,下文将要提到的 MASConstraintMaker 充当了所有约束的最终代理,如下图所示。MASCompositeConstraint 只是充当了转接和补充的作用。
请添加图片描述
至于关系操作方法、倍数操作方法、常量操作方法、优先级操作方法所调用的抽象方法。MASCompositeConstraint 对此的实现基本相同,都是对 childConstraints 中的约束进行遍历设置。

MASConstraintMaker

MASConstraintMakerMasonry 的核心。MASConstraintMaker 指定了构建布局的目标视图以及相关的约束。

@interface MASConstraintMaker () <MASConstraintDelegate>

@property (nonatomic, weak) MAS_VIEW *view;
@property (nonatomic, strong) NSMutableArray *constraints;

@end

MASConstraintMaker 提供了一系列只读的 MASConstraint 属性。这些属性在其 getter 方法内创建了对应 NSLayoutAttribute 枚举类型的约束项。这些属性包括以下:

@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
@property (nonatomic, strong, readonly) MASConstraint *leading;
@property (nonatomic, strong, readonly) MASConstraint *trailing;
@property (nonatomic, strong, readonly) MASConstraint *width;
@property (nonatomic, strong, readonly) MASConstraint *height;
@property (nonatomic, strong, readonly) MASConstraint *centerX;
@property (nonatomic, strong, readonly) MASConstraint *centerY;
@property (nonatomic, strong, readonly) MASConstraint *baseline;

#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)

@property (nonatomic, strong, readonly) MASConstraint *firstBaseline;
@property (nonatomic, strong, readonly) MASConstraint *lastBaseline;

#endif

#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)

@property (nonatomic, strong, readonly) MASConstraint *leftMargin;
@property (nonatomic, strong, readonly) MASConstraint *rightMargin;
@property (nonatomic, strong, readonly) MASConstraint *topMargin;
@property (nonatomic, strong, readonly) MASConstraint *bottomMargin;
@property (nonatomic, strong, readonly) MASConstraint *leadingMargin;
@property (nonatomic, strong, readonly) MASConstraint *trailingMargin;
@property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;

#endif

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

@property (nonatomic, strong, readonly) MASConstraint *edges;

上面提到,MASViewConstraintMASCompositeConstraint 都会利用其代理来创建并添加约束项,而它们的代理都是 MASConstraintMaker。那么,我们来看一下 MASConstraintMaker 对于 MASConstraintDelegate 的实现是怎么样的。

#pragma mark - MASConstraintDelegate

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

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    // 根据 约束属性 和 视图 创建一个约束单元
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    //创建约束,以约束单元作为约束的第一项
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    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;// 注意这一步,会对 make.top.left 这种情形产生关键影响
        //添加
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

我们先看 constraint:shouldBeReplacedWithConstraint: 方法,该方法的职责非常简单,就是在已有的约束中查找某个约束并进行替换。
我们再看constraint:addConstraintWithLayoutAttribute: 方法,该方法是被调用较多的一个方法,其职责主要就是创建并添加约束至 constraints 列表属性中。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值