该文章阅读的 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
文件中引用,那么子类就就要实现分类中的方法。