源碼閱讀:Masonry(四)——MASConstraint/MASConstraint+Private

NO IMAGE

該文章閱讀的 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<MASConstraintDelegate> 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 <Foundation/Foundation.h>
@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(八)——View+MASAdditions

源碼閱讀:Masonry(七)——MASConstraintMaker

源碼閱讀:Masonry(六)——MASViewConstraint

源碼閱讀:Masonry(五)——MASCompositeConstraint