原文連接ios
Masonry 是基於 Apple 的自動佈局封裝的一個輕量級佈局框架。Masonry 經過一種鏈式的 DSL(Domain-Specific Language)來描述 NSLayoutConstraint
。相比原生的自動佈局語法,Masonry 提供了更爲簡便的語法來構造佈局。Masonry 同時支持 iOS 和 Mac OS X。git
關於原生的自動佈局的詳細內容,能夠閱讀另外一篇文章——《系統理解 iOS 自動佈局》。github
本文所分析的 Masonry 源碼版本是 7.4.2
。數組
蘋果提供的自動佈局(Auto Layout)可以對視圖進行靈活有效的佈局。可是,使用原生的自動佈局相關的語法建立約束的過程是很是冗長的,可讀性也比較差。緩存
以下所示代碼,其做用是讓一個子視圖填充其父視圖,其中子視圖的每一邊相對父視圖縮進 10 像素。安全
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[superview addConstraints:@[
// view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],
]];
複製代碼
由上可見,使用原生的自動佈局語法,對於如此簡單的一個佈局,也是很是冗長的。若是使用 VFL(Visual Format Language)能夠有效減小冗餘,可是其 ASCII 類型語法使得編譯器沒法作類型檢查,存在必定的安全隱患。架構
Masonry 的目標其實就是 爲了解決原生自動佈局語法冗長的問題。對於上述示例,使用 Masonry 只須要一下幾行代碼便可解決。框架
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];
複製代碼
甚至還能夠更加簡單:less
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
複製代碼
Masonry 主要方法由上述例子就可一窺全貌。Masonry 主要經過對 UIView
(NSView
)、NSArray
、UIViewController
進行分類擴展,從而提供自動佈局的構建方法。相關方法定義在上圖所示部分文件中:ide
View+MASAddtions
NSArray+MASAddtions
ViewController+MASAddtions
經過分類提供的自動佈局構建方法主要有如下這些:
// View+MASAddtions
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
- (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
// NSArray+Addtions
- (NSArray *)mas_makeConstraints:(void (NS_NOESCAPE ^)(MASConstraintMaker *make))block;
- (NSArray *)mas_updateConstraints:(void (NS_NOESCAPE ^)(MASConstraintMaker *make))block;
- (NSArray *)mas_remakeConstraints:(void (NS_NOESCAPE ^)(MASConstraintMaker *make))block;
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
複製代碼
上述自動佈局構建方法均使用一個void(NS_NOESCAPE ^)(MASConstraintMaker *make)
類型的 block 做爲參數。的確,MASConstraintMaker
就是 Mansonry 框架中構建佈局約束的核心。MASConstraintMaker
引用了 MASConstraint
的一系列方法及其子類(包括:MASCompositeConstraint
、MASViewConstraint
),從而實現約束的建立與添加。
MASConstraint
則提供了一系列返回類型爲 MASConstraint
的方法,從而實現了鏈式 DSL,使 Masonry 具有了簡潔靈活的優勢。
下面,咱們依次來介紹 Masonry 框架中的幾個重要類:
MASLayoutConstraint
MASViewAttribute
MASConstraint
MAConstraintMaker
MASLayoutConstraint
類繼承自 NSLayoutConstraint
類。相比其父類,它就多了一個屬性 mas_key
。
MASLayoutConstraint
用來表示 佈局約束。
咱們知道在自動佈局系統中,約束的本質是一個方程式:
item1.attribute1 = multiplier × item2.attribute2 + constant
複製代碼
MASViewAttribute
就是約束方程式中一個 item
與 attribute
組成的單元。
以下所示即是 MASViewAttribute
定義的屬性。
@interface MASViewAttribute : NSObject
// The view which the reciever relates to. Can be nil if item is not a view.
@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;
@end
複製代碼
其中,關於 NSLayoutAttribute
枚舉所包含的類型,詳見 《系統理解 iOS 自動佈局》中 約束/約束規則/屬性 小節。
MASConstraint
是一個抽象類,主要爲其子類 MASViewConstraint
和 MASCompositeConstraint
聲明瞭一些共有的方法。MASConstraint
爲這些共有的方法實現了部分功能,底層的細節實現則由其子類決定。
根據約束方程式的組成,可將這些方法分爲如下幾類:
除此以外,還有優先級操做方法。
屬性操做方法
屬性操做方法根據對應的 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;
複製代碼
這些操做方法內部都是經過一個抽象方法實現,須由子類具體實現,該方法爲:
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute
複製代碼
關係操做方法
關係操做方法根據 NSLayoutRelation
枚舉類型建立約束關係項。
- (MASConstraint * (^)(id attr))equalTo;
- (MASConstraint * (^)(id attr))greaterThanOrEqualTo;
- (MASConstraint * (^)(id attr))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
是 MASConstraint
的子類,能夠稱之爲 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
實現的父類抽象方法。
首先,屬性操做方法所調用的一個抽象方法。
- (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
其實將該方法的具體實現交給了它的代理。
其次,關係操做方法所調用的一個抽象方法。
- (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
方法實現了不一樣的邏輯。
接着,倍數操做方法所調用的兩個抽象方法。
- (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
。
而後,常量操做方法所調用的幾個抽象方法。
// 只有約束方程式第一項的屬性是:
// NSLayoutAttributeLeft、NSLayoutAttributeLeading、
// NSLayoutAttributeTop、NSLayoutAttributeBottom、
// NSLayoutAttributeRight、NSLayoutAttributeTrailing
// 時,方法纔會有效設置常量屬性
- (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
的類型來設置常量屬性。當屬性不匹配值,對常量屬性的設置並不會生效。
最後,優先級操做方法的一個抽象方法。
- (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
也是 MASConstraint
的子類。與 MASViewConstraint
只表示一個約束不一樣,MASCompositeConstraint
能夠表示一組約束。
@interface MASCompositeConstraint () <MASConstraintDelegate>
@property (nonatomic, strong) id mas_key;
@property (nonatomic, strong) NSMutableArray *childConstraints;
@end
複製代碼
其中,childConstraints
屬性持有了一組約束。
咱們再來看一下 MASCompositeConstraint
實現的父類抽象方法。
首先,屬性操做方法所調用的一個抽象方法。
- (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;
}
複製代碼
能夠看出,該方法內部將經過其代理新建立的普通約束或組合約束添加至 MASCompositeConstraint
的 childConstraints
數組中,並設置子約束的代理爲 MASCompositeConstraint
的代理。
事實上,在 Masonry 中,下文將要提到的 MASConstraintMaker
充當了全部約束的最終代理,以下圖所示。MASCompositeConstraint
只是充當了轉接和補充的做用。
至於關係操做方法、倍數操做方法、常量操做方法、優先級操做方法所調用的抽象方法。MASCompositeConstraint
對此的實現基本相同,都是對 childConstraints
中的約束進行遍歷設置。
MASConstraintMaker
是 Masonry 的核心。
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;
@property (nonatomic, strong, readonly) MASConstraint *firstBaseline;
@property (nonatomic, strong, readonly) MASConstraint *lastBaseline;
@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;
@property (nonatomic, strong, readonly) MASConstraint *edges;
@property (nonatomic, strong, readonly) MASConstraint *size;
@property (nonatomic, strong, readonly) MASConstraint *center;
@property (nonatomic, strong, readonly) MASConstraint *(^attributes)(MASAttribute attrs);
複製代碼
上面提到,MASViewConstraint
和 MASCompositeConstraint
都會利用其代理來建立並添加約束項,而它們的代理都是 MASConstraintMaker
。那麼,咱們來看一下 MASConstraintMaker
對於 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]) {
// 若是是在已有約束的基礎上再建立的約束,則將它們轉換成一個 組合約束,並將原約束替換成該組合約束。
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;
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
複製代碼
咱們先看 constraint:shouldBeReplacedWithConstraint:
方法,該方法的職責很是簡單,就是在已有的約束中查找某個約束並進行替換。
咱們再看constraint:addConstraintWithLayoutAttribute:
方法,該方法是被調用較多的一個方法,其職責主要就是建立並添加約束至 constraints
列表屬性中。
在瞭解了 Masonry 的基本組成以後,咱們再經過一個示例來介紹一下 Masonry 的工做流程。
示例以下所示。
[view mas_makeConstraints::^(MASConstraintMaker *make) {
make.top.equalTo(@10);
make.left.equalTo(superview.mas_left).offset(10);
make.width.height.equalTo(@100);
}];
複製代碼
首先執行分類方法 mas_makeConstraints:
。
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
複製代碼
方法內部先設置 translatesAutoresizingMaskIntoConstraints
爲 NO
。由於,Autoresize Mask 和 Auto Layout 是兩套佈局系統,前者默承認以轉換成後者。爲了不前者對自動佈局系統產生干擾,這裏須要關閉佈局轉換。
方法內部還會建立一個 MASConstraintMaker
實例,而後以此爲參數調用 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) {
self.layoutConstraint.active = YES;
[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
// 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
,本質上仍是調用 MASViewConstraint
的 install
方法。該方法根據 MASViewConstraint
的各個屬性建立一個原生的約束(NSLayoutConstraint
類型),並在定位約束層級後,將約束添加到相應層級的視圖上。
下面,咱們再來看看執行 block 又發生了什麼。
首先,看一下 make.top.equalTo(@10);
的執行流程。
// MASConstraintMaker
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (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]) {
// ...
}
if (!constraint) {
// 若是不是在已有約束的基礎上再建立約束,則添加約束至列表
newConstraint.delegate = self; // 注意這一步,會對 make.top.left 這種情形產生關鍵影響,詳見下文
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
// -----------------------------------------------------------------
// 至此,make.top 執行完畢
// -----------------------------------------------------------------
// MASConstraint
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
// attribute 多是 @0 相似的值,也多是 view.mas_width等這樣的
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
// MASViewConstraint
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
if ([attribute isKindOfClass:NSArray.class]) {
// ...
} 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; // 設置約束常量
}
// -----------------------------------------------------------------
// 至此,make.top.equalTo(@10) 執行完畢
// -----------------------------------------------------------------
複製代碼
而後,咱們再看 make.left.equalTo(superview.mas_left).offset(10);
的執行流程。 其實,這個執行流程也就是執行 equalTo
內部的 setSecondViewAttribute
時有所不一樣。另外,offset
方法作了一步額外的操做。
// MASViewConstraint
- (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);
}
}
// -----------------------------------------------------------------
// 至此,make.left.equalTo(superview.mas_left) 執行完畢
// -----------------------------------------------------------------
// MASConstraint
- (MASConstraint * (^)(CGFloat))offset {
return ^id(CGFloat offset){
self.offset = offset;
return self;
};
}
- (void)setOffset:(CGFloat)offset {
self.layoutConstant = offset;
}
// -----------------------------------------------------------------
// 至此,make.left.equalTo(superview.mas_left).offset(10) 執行完畢
// -----------------------------------------------------------------
複製代碼
最後,咱們再看 make.width.height.equalTo(@100);
的執行流程。 其實到 make.width
這一步與前面沒有什麼差異,再執行 height
時出現了轉換。
// MASConstraint
- (MASConstraint *)height {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}
// MASViewConstraint
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
// 見上述 make.top.equalTo(@10) 分析代碼中的介紹,此時 self.delegate 早已被設置成了 NSConstraintMaker 了
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
// MASConstraintMaker
- (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]) {
// 若是是在已有約束的基礎上再建立的約束,則將它們轉換成一個 組合約束,並將原約束替換成該組合約束。
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
// 這裏會將原來 make.width 添加的約束 替換成一個 組合約束(寬度約束 + 高度約束)
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
// 返回組合約束
return compositeConstraint;
}
if (!constraint) {
// ...
}
// ...
}
- (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];
}
// -----------------------------------------------------------------
// 至此,make.width.height 執行完畢
// -----------------------------------------------------------------
// MASConstraint
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
// attribute 多是 @0 相似的值,也多是 view.mas_width等這樣的
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
// MASCompositeConstraint
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attr, NSLayoutRelation relation) {
// CompositeConstraint 的 childConstraits 中每一項,調用 equalToWithRelation
for (MASConstraint *constraint in self.childConstraints.copy) {
constraint.equalToWithRelation(attr, relation);
}
return self;
};
}
// -----------------------------------------------------------------
// 至此,make.width.height.equalTo(@100) 執行完畢
// -----------------------------------------------------------------
複製代碼
Masonry 巧妙利用了面向對象的繼承、多態思想以及 block 的特性,從而實現了很是簡便的鏈式 DSL,極大地提高了自動佈局開發的效率。
(完)