思想
链式语法有什么优势?
- 链式语法提供了一种清晰、可读的方法调用方式,通过连续的方法链表达代码逻辑,提高了代码的表达力和可维护性。
为什么Masonry会使用链式语法?
- 因为原生的AutoLayout设置Constraint的代码真的究极冗长,使用链式语法来进行约束设置,不仅能减少代码量,还能提高可读性。
示例代码如下:
// 示例代码,这是部分 viewDidLoad中的代码
UIView *view1 = [[UIView alloc] init];
[self.view addSubview:view1]
UIView *view2 = [[UIView alloc] init];
[self.view addSubview:view2];
// masonry的约束设置方法
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.equalTo(self.view).offset(50);
make.right.equalTo(self.view.mas_centerX).offset(-70);
make.bottom.equalTo(self.view.mas_centerY).offset(-100);
}];
1.首先,我们会想到属性
- 因为链式语法是通过点语法实现的,而oc中常见的点语法是用于访问属性的,所以我们很自然的会想到使用一层一层的属性来形成点语法。
例如:
// 假设ClassA中有个属性为ClassB *b,而ClassB中有个属性为ClassC *c
ClassA *a = [ClassA new];
a.b.c = ?
- 当我们尝试去实现之后,发现有一个很大的问题:属性的点语法和上面的例子中的.offset(50)有个很大的差别,那就是没法传值
2.其次,我们想想Block
既然涉及到了传值,那我们肯定得往方法和block上想啊。
而既能传值,又能通过点语法调用的有:
1. Block属性
2. 返回值为Block的方法。
例如:
@interface ClassB : NSObject
@property (nonatomic) NSInteger count;
@end
@interface ClassA : NSObject
// block属性
@property (nonatomic, readonly) ClassB *(^set_count_1)(NSInteger);
// 返回值为block的方法
- (ClassB *(^)(NSInteger))set_count_2;
@end
@implementation ClassB
@end
@implementation ClassA
- (ClassB *(^)(NSInteger))set_count_1 {
// 有小伙伴在这里可能会感到疑惑,这里返回的是一个: 参数为integer,返回值为 ClassB的BLock。调用setCount这个getter方法,只是返回这个Block而已,又没有调用这个BLock。
// 有一说一确实,如果是这样用: id b = a.setCount; 那b确实==block。
// 但如果这样用:id b = a.setCount(1); 那b就是Block调用后的返回值了。
return ^(NSInteger count) {
ClassB *b = [ClassB new];
b.count = count;
return b
};
}
- (ClassB *(^)(NSInteger))set_count_2 {
return ^(NSInteger count) {
ClassB *b = [ClassB new];
b.count = count;
return b
};
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
ClassA *a = [ClassA new];
// 这里就得到了一个b。因为a调用了setCount这个block,传入了50这个参数,然后返回了一个ClassB对象。
ClassB *b = a.set_count_1(50);
// 同理,但这个是方法,上面那个是block属性
b = a.set_count_2(60);
}
- 这里,我们实现了使用点语法加传参数,只需要再改进一下,就可以得到链式语法了。你是不是会想,在ClassB里面再实现一个类似的block属性,然后再返回一个ClassC之类的东西,然后一直下去,直到某个最终的类?这样可以是可以,但只能规定链必须严格按照这个方向去执行(a->b->c...)。如果我要打乱顺序(a->c->b...),那就不行了。
- 所以,你肯定也能想到:我们把这些所有的类(ClassA,B,C)都变为同一个抽象类不就行了?Masonry就是这么做的。
3.最后,我们看看Masonry
- 从上面的示例代码我们可以知道,链式语法是从MASConstraintMaker开始的,所以我们直接去看它的源码。(不用怕它多,提取一下它们的共性就好,不用太用力的看)
// MASConstraintMaker.h 的部分源码
@interface MASConstraintMaker : NSObject
/**
* The following properties return a new MASViewConstraint
* with the first item set to the makers associated view and the appropriate MASViewAttribute
*/
@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
.
.
.
// 只看看部分源码,剩下的可以直接去github上看
@end
- 我们发现,它的属性都是MASConstraint类型。那我们再去看看它的源码
// MASConstraint.h部分源码
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
*/
- (MASConstraint * (^)(MASEdgeInsets insets))insets;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeTop, NSLayoutAttributeLeft, NSLayoutAttributeBottom, NSLayoutAttributeRight
*/
- (MASConstraint * (^)(CGFloat inset))inset;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeWidth, NSLayoutAttributeHeight
*/
- (MASConstraint * (^)(CGSize offset))sizeOffset;
/**
* Modifies the NSLayoutConstraint constant,
* only affects MASConstraints in which the first item's NSLayoutAttribute is one of the following
* NSLayoutAttributeCenterX, NSLayoutAttributeCenterY
*/
- (MASConstraint * (^)(CGPoint offset))centerOffset;
/**
* Modifies the NSLayoutConstraint constant
*/
- (MASConstraint * (^)(CGFloat offset))offset;
- 看到没,出现了大量的block。根据我们前面的联想,我们应该抓住出现block的地方。接着往下看看它们的实现。
// MASConstraint.m部分源码
#pragma mark - NSLayoutConstraint constant proxies
- (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;
};
}
- 这上面的实现都有一个共同点:把自身的某个属性设置为传入的参数,然后这个block再返回self。这正是链式语法的可以换序的关键。
- 正是这个return self,使我们可以换序使用点语法:因为return self,在点语法的返回值中你还是当前对象,你还能继续再使用当前对象的点语法。(看不明白的话其实把Masonry pod下来debug一下就好了)
通过return self,我们可以把一个constraint对象的属性用多个点语法语义清晰地设置清楚。
当然,并不是所有所有的点语法的实现都是直接return self。有可能它要通过某个方法去处理一些情况判断(其实最终都还是return self)
例如:
// MASConstraint.m
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
- 再看看里面这个方法的实现,这个方法在两个子类里其实有两种实现,但是基本一样,我们只看最关键的代码
// MASViewConstraint.m
- (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;
}
};
}
- if 和 else 两部分的思想基本是一样的。我们只看简单的else。
} else {
NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
self.layoutRelation = relation;
self.secondViewAttribute = attribute;
return self;
}
其实很清晰明了,还是跟前面那些方法的实现一样,根据参数设好自己的属性,然后再return self
4.送佛送到西
@interface ClassA : NSObject
@property (nonatomic, assign) NSInteger width;
@property (nonatomic, assign) NSInteger height;
@property (nonatomic, readonly) ClassA *(^withWidth)(NSInteger);
- (ClassA *(^)(NSInteger))andHeight;
@end
@implementation ClassA
- (ClassA * _Nonnull (^)(NSInteger))withWidth {
return ^(NSInteger width) {
self.width = width;
return self;
};
}
- (ClassA * _Nonnull (^)(NSInteger))andHeight {
return ^(NSInteger height) {
self.height = height;
return self;
};
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
ClassA *a = [ClassA new];
a.withWidth(50).andHeight(60); // 链式语法在这,这两个点语法反序调用也一样的结果
NSLog(@"width:%ld height:%ld", (long)a.width, (long)a.height);
// 结果:width:50 height:60
}