源码阅读:Masonry(四)—— MASConstraint/MASConstraint+Private

该文章阅读的 Masonry 的版本为 1.1.0。

这个类我们可以称它为“约束基类”,也可以说它是一个抽象类,它定义了一堆接口,却没有具体实现,具体的实现都留给了其子类,但它实现了链式编程的效果。

1.类扩展 MASConstraint+Private

从这个类扩展的名字中,我们就能得知这个是用于私有,并不对外暴露。

1.1 MASConstraintDelegate 协议

- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint;

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;
复制代码

当需要替换约束对象时,必须要实现协议中的这两个方法。

1.2 扩展

@property (nonatomic, assign) BOOL updateExisting;
复制代码

这个属性代表当设置约束时,是否检查约束已被安装过了


@property (nonatomic, weak) id delegate;
复制代码

实现协议的对象


- (void)setLayoutConstantWithValue:(NSValue *)value;
复制代码

将 NSValue 类型数据转换成基本数据类型的方法,这个方法 MASConstraint 类中已经实现了,其子类只需调用即可。

1.3 Abstract 分类

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation;
复制代码

设置约束关系的方法。


- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;
复制代码

添加约束属性的方法。


添加这个分类的意义是,引用这个文件的 MASConstraint 的子类必须实现这两个方法。

2.公共宏定义

#define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...)    greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...)       lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))

#define mas_offset(...)                  valueOffset(MASBoxValue((__VA_ARGS__)))


#ifdef MAS_SHORTHAND_GLOBALS

#define equalTo(...)                     mas_equalTo(__VA_ARGS__)
#define greaterThanOrEqualTo(...)        mas_greaterThanOrEqualTo(__VA_ARGS__)
#define lessThanOrEqualTo(...)           mas_lessThanOrEqualTo(__VA_ARGS__)

#define offset(...)                      mas_offset(__VA_ARGS__)

#endif
复制代码

这个宏定义可分为两部分:

  • #ifdef MAS_SHORTHAND_GLOBALS 之前的部分,定义了像 mas_equalTo 这类方便数据装箱的方法。如:
make.height.mas_equalTo(200.0);
复制代码

我们可以直接传递基本数据类型,具体的装箱工作由 Masonry 内部完成。

  • 之后的部分是用来简化代码的宏,也就是去掉前面的 mas_。你只需要在 #import "Masonry.h" 之前定义 MAS_SHORTHAND_GLOBALS 这个宏,如:
#define MAS_SHORTHAND_GLOBALS
#import "Masonry.h"
复制代码

那么,上面那句设置约束的代码就可以简写成这样:

make.height.equalTo(200.0);
复制代码

3.公共方法

3.1 链式编程的实现

/**
 设置视图上左下右各自的边距
 */
- (MASConstraint * (^)(MASEdgeInsets insets))insets;
- (void)setInsets:(MASEdgeInsets)insets;
复制代码
/**
 设置视图上左下右的边距都相等
 */
- (MASConstraint * (^)(CGFloat inset))inset;
- (void)setInset:(CGFloat)inset;
复制代码
/**
 设置视图长宽的偏移量
 */
- (MASConstraint * (^)(CGSize offset))sizeOffset;
- (void)setSizeOffset:(CGSize)sizeOffset;
复制代码
/**
 设置视图中心点的偏移量
 */
- (MASConstraint * (^)(CGPoint offset))centerOffset;
- (void)setCenterOffset:(CGPoint)centerOffset;
复制代码
/**
 设置偏移值量,也就是 NSLayoutConstraint 中的 c
 */
- (MASConstraint * (^)(CGFloat offset))offset;
- (void)setOffset:(CGFloat)offset;
复制代码
/**
 设置 NSValue 类型的偏移量,也就是说 Masonry 会自己判断传入的数据类型,并设置对应的值
 */
- (MASConstraint * (^)(NSValue *value))valueOffset;
复制代码
/**
 设置乘数,也就是 NSLayoutConstraint 中的 multiplier
 */
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy;
复制代码
/**
 设置除数,其实也就是乘数,乘数是 1.0/dividedBy
 */
- (MASConstraint * (^)(CGFloat divider))dividedBy;
复制代码
/**
 设置约束的优先级
 */
- (MASConstraint * (^)(MASLayoutPriority priority))priority;
复制代码
/**
 设置约束为低优先级
 */
- (MASConstraint * (^)(void))priorityLow;
复制代码
/**
 设置约束为中优先级
 */
- (MASConstraint * (^)(void))priorityMedium;
复制代码
/**
 设置约束为高优先级
 */
- (MASConstraint * (^)(void))priorityHigh;
复制代码
/**
 设置约束关系为 NSLayoutRelationEqual,也就是相等
 */
- (MASConstraint * (^)(id attr))equalTo;
复制代码
/**
 设置约束关系为 NSLayoutRelationGreaterThanOrEqual,也就是不小于
 */
- (MASConstraint * (^)(id attr))greaterThanOrEqualTo;
复制代码
/**
 设置约束关系为 NSLayoutRelationLessThanOrEqual,也就是不大于
 */
- (MASConstraint * (^)(id attr))lessThanOrEqualTo;
复制代码
/**
 只是为了提高可读性,可写可不写
 */
- (MASConstraint *)with;
复制代码
/**
 和上面的 with 相同
 */
- (MASConstraint *)and;
复制代码
/**
 下面这些方法就对应着约束属性的枚举值 NSLayoutAttribute
 */
- (MASConstraint *)left;
- (MASConstraint *)top;
- (MASConstraint *)right;
- (MASConstraint *)bottom;
- (MASConstraint *)leading;
- (MASConstraint *)trailing;
- (MASConstraint *)width;
- (MASConstraint *)height;
- (MASConstraint *)centerX;
- (MASConstraint *)centerY;
- (MASConstraint *)baseline;
- (MASConstraint *)firstBaseline;
- (MASConstraint *)lastBaseline;
- (MASConstraint *)leftMargin;
- (MASConstraint *)rightMargin;
- (MASConstraint *)topMargin;
- (MASConstraint *)bottomMargin;
- (MASConstraint *)leadingMargin;
- (MASConstraint *)trailingMargin;
- (MASConstraint *)centerXWithinMargins;
- (MASConstraint *)centerYWithinMargins;
复制代码

3.2 约束的安装卸载

/**
 约束安装,和下面的 install 方法是一样的
 */
- (void)activate;
复制代码
/**
 约束卸载,和下面的 uninstall 方法是一样的
 */
- (void)deactivate;
复制代码
/**
 约束安装
 */
- (void)install;
复制代码
/**
 约束卸载
 */
- (void)uninstall;
复制代码

3.3 帮助宏自动补全

- (MASConstraint * (^)(id attr))mas_equalTo;
- (MASConstraint * (^)(id attr))mas_greaterThanOrEqualTo;
- (MASConstraint * (^)(id attr))mas_lessThanOrEqualTo;
- (MASConstraint * (^)(id offset))mas_offset;
复制代码

这四个方法用来帮助用户在使用本文第 1 节中看到的宏的时候自动补全。

4.私有宏定义

#define MASMethodNotImplemented() \
    @throw [NSException exceptionWithName:NSInternalInconsistencyException \
                                   reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
                                 userInfo:nil]
复制代码

这个宏定义了一个自定义的异常对象。

5.方法实现

5.1 初始化方法

- (id)init {
	NSAssert(![self isMemberOfClass:[MASConstraint class]], @"MASConstraint is an abstract class, you should not instantiate it directly.");
	return [super init];
}
复制代码

在这个方法的重写中,添加了一句断言。旨在禁止该类被实例化。

5.2 抽象方法

- (MASConstraint * (^)(CGFloat multiplier))multipliedBy { MASMethodNotImplemented(); }

- (MASConstraint * (^)(CGFloat divider))dividedBy { MASMethodNotImplemented(); }

- (MASConstraint * (^)(MASLayoutPriority priority))priority { MASMethodNotImplemented(); }

- (MASConstraint * (^)(id key))key { MASMethodNotImplemented(); }

- (void)setInsets:(MASEdgeInsets __unused)insets { MASMethodNotImplemented(); }

- (void)setInset:(CGFloat __unused)inset { MASMethodNotImplemented(); }

- (void)setSizeOffset:(CGSize __unused)sizeOffset { MASMethodNotImplemented(); }

- (void)setCenterOffset:(CGPoint __unused)centerOffset { MASMethodNotImplemented(); }

- (void)setOffset:(CGFloat __unused)offset { MASMethodNotImplemented(); }

- (MASConstraint *)animator { MASMethodNotImplemented(); }

- (void)activate { MASMethodNotImplemented(); }

- (void)deactivate { MASMethodNotImplemented(); }

- (void)install { MASMethodNotImplemented(); }

- (void)uninstall { MASMethodNotImplemented(); }
复制代码

我不太清除怎么统称上面的这样方法,因为在源码中 Masonry 用了 Abstract 这个单词作为标识,所以我就直接用该单词直返过来的意思作这节的标题了。

可以看到,这些方法都没有被实现,反而,如果真的去调用这些方法,还会报错。

但是这些方法在该类的子类中都被实现了,由于该类本身就不允许被实例化,所以这些方法也不会被调用。

在实际使用中,由于被实例化的都是该类的子类,所以在调用这些方法的时候,都是调用对应子类中的方法。

这其实就是利用 Objective-C 语言多态的特性。

4.3 约束关系相关方法

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

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

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

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

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

- (MASConstraint * (^)(id))mas_lessThanOrEqualTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationLessThanOrEqual);
    };
}
复制代码

这些方法的实现都相同,只不过根据方法的不同传递了不同的参数。

4.4 约束优先级相关方法

- (MASConstraint * (^)(void))priorityLow {
    return ^id{
        self.priority(MASLayoutPriorityDefaultLow);
        return self;
    };
}

- (MASConstraint * (^)(void))priorityMedium {
    return ^id{
        self.priority(MASLayoutPriorityDefaultMedium);
        return self;
    };
}

- (MASConstraint * (^)(void))priorityHigh {
    return ^id{
        self.priority(MASLayoutPriorityDefaultHigh);
        return self;
    };
}
复制代码

同样也是相同的实现,不同的参数。

4.5 约束常数相关方法

- (MASConstraint * (^)(MASEdgeInsets))insets {
    return ^id(MASEdgeInsets insets){
        self.insets = insets;
        return self;
    };
}

- (MASConstraint * (^)(CGFloat))inset {
    return ^id(CGFloat inset){
        self.inset = inset;
        return self;
    };
}

- (MASConstraint * (^)(CGSize))sizeOffset {
    return ^id(CGSize offset) {
        self.sizeOffset = offset;
        return self;
    };
}

- (MASConstraint * (^)(CGPoint))centerOffset {
    return ^id(CGPoint offset) {
        self.centerOffset = offset;
        return self;
    };
}

- (MASConstraint * (^)(CGFloat))offset {
    return ^id(CGFloat offset){
        self.offset = offset;
        return self;
    };
}

- (MASConstraint * (^)(NSValue *value))valueOffset {
    return ^id(NSValue *offset) {
        NSAssert([offset isKindOfClass:NSValue.class], @"expected an NSValue offset, got: %@", offset);
        [self setLayoutConstantWithValue:offset];
        return self;
    };
}

- (MASConstraint * (^)(id offset))mas_offset {
    // Will never be called due to macro
    return nil;
}
复制代码

这些方法的实现都是直接调用了抽象方法,等待其子类实现具体的功能。

4.6 语义相关方法

- (MASConstraint *)with {
    return self;
}

- (MASConstraint *)and {
    return self;
}
复制代码

这两个方法什么都没做,只是将调用他的对象返回,其目的只是为了提高代码的可读性。

4.6 约束属性相关方法

- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}

- (MASConstraint *)right {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}

- (MASConstraint *)bottom {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}

- (MASConstraint *)leading {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeading];
}

- (MASConstraint *)trailing {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailing];
}

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

- (MASConstraint *)height {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}

- (MASConstraint *)centerX {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterX];
}

- (MASConstraint *)centerY {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterY];
}

- (MASConstraint *)baseline {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBaseline];
}

- (MASConstraint *)firstBaseline {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeFirstBaseline];
}
- (MASConstraint *)lastBaseline {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLastBaseline];
}

- (MASConstraint *)leftMargin {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeftMargin];
}

- (MASConstraint *)rightMargin {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRightMargin];
}

- (MASConstraint *)topMargin {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTopMargin];
}

- (MASConstraint *)bottomMargin {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottomMargin];
}

- (MASConstraint *)leadingMargin {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeadingMargin];
}

- (MASConstraint *)trailingMargin {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTrailingMargin];
}

- (MASConstraint *)centerXWithinMargins {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterXWithinMargins];
}

- (MASConstraint *)centerYWithinMargins {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterYWithinMargins];
}
复制代码

同样是调用相同方法,传递不同参数。

4.7 类扩展方法

- (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);
    }
}
复制代码

该方法用于将传入的 NSValue 类型的数据转换成对应的基本数据类型,然后调用相关的 setter 方法。

4.8 Abstract 分类方法

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { MASMethodNotImplemented(); }
复制代码
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
    MASMethodNotImplemented();
}
复制代码

这两个方法并没有具体实现,等待子类实现。

6.总结

这个类的内容虽然不多,但是可以学习的东西还挺多的。

6.1 链式编程的实现

从源码中可以看出,链式编程的实现是依靠在实现方法时,方法的返回值是一个 block,该 block 的返回值是本对象。

下面我们就可以仿照写一个小 Demo 来实现最简单的计算器:

  • WTCalculator.h
#import 

@class WTCalculator;

typedef WTCalculator *(^WTChainingBlock)(float input);

@interface WTCalculator : NSObject

@property (nonatomic, assign) float result;

- (WTChainingBlock)add;
- (WTChainingBlock)subtract;
- (WTChainingBlock)multiply;
- (WTChainingBlock)divide;

@end
复制代码
  • WTCalculator.m
#import "WTCalculator.h"

@implementation WTCalculator

- (instancetype)init {
    if (self = [super init]) {
        self.result = 0;
    }
    return self;
}

- (WTChainingBlock)add {
    return ^WTCalculator *(float input) {
        self.result += input;
        return self;
    };
}

- (WTChainingBlock)subtract {
    return ^WTCalculator *(float input) {
        self.result -= input;
        return self;
    };
}

- (WTChainingBlock)multiply {
    return ^WTCalculator *(float input) {
        self.result *= input;
        return self;
    };
}

- (WTChainingBlock)divide {
    return ^WTCalculator *(float input) {
        self.result *= (1.0 / input);
        return self;
    };
}

@end
复制代码
  • 使用
WTCalculator *calculator = [WTCalculator new];
calculator.add(36).subtract(19).multiply(75).divide(42);
NSLog(@"%.2f", calculator.result);
复制代码

6.2 如何写好一个基类/抽象类

定义必须的属性和方法,如果具体实现因不同的子类而定,就利用 Objective-C 语言多态的特性将实现留给子类去实现,否则就由基类实现。

6.3 如何让子类也能调用父类实现的私有方法

定义一个类扩展文件,在其中定义方法。在父类和子类的 .m 文件中同时引用这个类扩展文件,只要父类实现了定义的方法,在子类中就可以直接调用。

6.4 如何让子类必须实现某些私有方法

定义一个分类文件,在其中定义方法。在子类的 .m 文件中引用,那么子类就就要实现分类中的方法。

你可能感兴趣的:(源码阅读:Masonry(四)—— MASConstraint/MASConstraint+Private)