Masonry is a light-weight layout framework which wraps AutoLayout with a nicer syntax. Masonry has its own layout DSL which provides a chainable way of describing your NSLayoutConstraints which results in layout code that is more concise and readable. Masonry supports iOS and Mac OS X.
翻譯:
Masonry是一個(gè)輕量級(jí)的布局框架粘茄,它使用更好的語(yǔ)法包裝AutoLayout。 Masonry有自己的布局DSL筑悴,它提供了一種鏈?zhǔn)秸{(diào)用的方式來(lái)描述NSLayoutConstraints监右,從而使布局代碼更簡(jiǎn)潔抖僵,更易讀。 Masonry支持iOS和Mac OS X.
什么是DSL
DSL(Domain Specific Language) 翻譯成中文就是:“領(lǐng)域特定語(yǔ)言”。首先狗唉,從定義就可以看出,DSL 也是一種編程語(yǔ)言涡真,只不過(guò)它主要是用來(lái)處理某個(gè)特定領(lǐng)域的問(wèn)題分俯。
下邊介紹iOS中如何實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用的DSL。
為什么需要使用Masonry
首先看下直接用NSLayoutConstraints
方式布局視圖需要什么操作:
例如:我們需要布局一個(gè)視圖view1
哆料,使他距離父視圖上下左右都為10缸剪,NSLayoutConstraints
布局代碼如下:
公式:view1.top
= superview.top
* 1.0 + 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],
]];
Masonry代碼如下:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(superview).with.insets(padding);
}];
由此可以看到使用NSLayoutConstraints
方式布局代碼及其冗余且不易讀。
Masonry框架結(jié)構(gòu)分析
主要的幾個(gè)類(lèi):
- View+MASAdditions.h
- MASConstraintMaker
- MASViewConstraint
從調(diào)用mas_makeConstraints
方法說(shuō)起
首先我們看一個(gè)簡(jiǎn)單調(diào)用的例子:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(superview).offset(10)
}];
mas_makeConstraints:
實(shí)現(xiàn)如下:首先將self.translatesAutoresizingMaskIntoConstraints
置為NO
东亦,關(guān)閉自動(dòng)添加約束杏节,改為我們手動(dòng)添加,接著創(chuàng)建一個(gè)MASConstraintMaker
對(duì)象典阵,通過(guò)block將constraintMaker
對(duì)象回調(diào)給用戶(hù)奋渔,讓用戶(hù)對(duì)constraintMaker
對(duì)象的屬性進(jìn)行初始化,其中block(constraintMaker)
就相當(dāng)于我們直接在該方法內(nèi)部調(diào)用make.left.mas_equalTo(superview).offset(10)
壮啊,然后調(diào)用install
方法對(duì)約束進(jìn)行安裝嫉鲸,該方法返回一個(gè)數(shù)組,數(shù)組當(dāng)中存放約束數(shù)組他巨,成員類(lèi)型為MASViewConstraint
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
緊接著看下block
回調(diào)回來(lái)的操作是如何進(jìn)行的充坑,也就是下面的這些代碼:
make.left.mas_equalTo(superview).offset(10)
點(diǎn)擊去看下.left
的調(diào)用實(shí)現(xiàn):
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
最后調(diào)用:
- (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]) {
//replace with composite constraint
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;
}
其中有一個(gè)MASViewAttribute
的類(lèi),該類(lèi)其實(shí)是對(duì)UIView
和NSLayoutAttribute
的封裝
MASViewConstraint
是對(duì)NSLayoutConstraint
的封裝染突,最后將布局約束添加到一個(gè)數(shù)組當(dāng)中
block
回調(diào)執(zhí)行完畢之后捻爷,最后對(duì)布局進(jìn)行安裝[constraintMaker install]
,該方法份企,最后會(huì)調(diào)用到MASViewConstraint
當(dāng)中的install
方法也榄,其中有一個(gè)比較重要的方法:
MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
MAS_VIEW *closestCommonSuperview = nil; // 定義一個(gè)公共父視圖
MAS_VIEW *secondViewSuperview = view;
while (!closestCommonSuperview && secondViewSuperview) { // 遍歷secondView的父視圖
MAS_VIEW *firstViewSuperview = self;
while (!closestCommonSuperview && firstViewSuperview) { // 遍歷當(dāng)前視圖的父視圖
if (secondViewSuperview == firstViewSuperview) {
closestCommonSuperview = secondViewSuperview; // 找到公共父視圖
}
firstViewSuperview = firstViewSuperview.superview;
}
secondViewSuperview = secondViewSuperview.superview;
}
return closestCommonSuperview;
}
該方法是查找兩個(gè)視圖最近的公共父視圖,這個(gè)類(lèi)似求兩個(gè)數(shù)字的最小公倍數(shù)。如果找到了就返回甜紫,如果找不到就返回nil降宅。尋找兩個(gè)視圖的公共父視圖對(duì)于約束的添加來(lái)說(shuō)是非常重要的,因?yàn)橄鄬?duì)的約束是添加到其公共父視圖上的囚霸。比如舉個(gè)列子 viewA.left = viewB.right + 10, 因?yàn)槭莢iewA與viewB的相對(duì)約束腰根,那么約束是添加在viewA與viewB的公共父視圖上的,如果viewB是viewA的父視圖拓型,那么約束就添加在viewB上從而對(duì)viewA起到約束作用额嘿。
遇到的問(wèn)題
MAS_SHORTHAND 和 MAS_SHORTHAND_GLOBALS的含義
- MAS_SHORTHAND: 如果我們用
Masonry
框架不想寫(xiě)mas_
前綴,需要在導(dǎo)入頭文件之前定義這個(gè)宏 - MAS_SHORTHAND_GLOBALS: 定義這個(gè)宏劣挫,可以讓
equalTo
接受基本數(shù)據(jù)類(lèi)型册养,內(nèi)部會(huì)對(duì)基本數(shù)據(jù)類(lèi)型進(jìn)行封裝
//define this constant if you want to use Masonry without the 'mas_' prefix
#define MAS_SHORTHAND
//define this constant if you want to enable auto-boxing for default syntax
#define MAS_SHORTHAND_GLOBALS
#import "Masonry.h"
Convenience initializer
和 designated initializer
在我們閱讀masonry
源碼的過(guò)程中,我們發(fā)現(xiàn)有兩個(gè)初始化方法压固,注釋不太一樣球拦,位于MASViewAttribute
類(lèi)下:
/**
* Convenience initializer.
*/
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute;
/**
* The designated initializer.
*/
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute;
這兩種初始化的方式有什么區(qū)別呢,平時(shí)在我們開(kāi)發(fā)當(dāng)中帐我,我們可能需要通過(guò)初始化來(lái)確定一些屬性的值坎炼,并不想由外界來(lái)修改它,于是我們可能會(huì)需要些很多個(gè)initWith
方法拦键,加入我的這個(gè)對(duì)象有姓名点弯、性別、年齡等屬性矿咕,但是我初始化的時(shí)候,并不是所有地方都要知道這些信息狼钮,于是我們可能會(huì)有initWithUserName:
碳柱、initWithSex:
、initWithUserName:age:
等方法熬芜,為了管理我們的initWith
方法莲镣,Apple
將 init
方法分為兩種類(lèi)型:designated initializer
和 convenience initializer
(又叫 secondary initializer
)
designated initializer
只有一個(gè),它會(huì)為 class
當(dāng)中每個(gè) property
都提供一個(gè)初始值涎拉,是最完整的 initWith
方法瑞侮,例如我們?cè)?code>masonry當(dāng)中也可以看到:
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
return self;
}
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [super init];
if (!self) return nil;
_view = view;
_item = item;
_layoutAttribute = layoutAttribute;
return self;
}
最終都會(huì)調(diào)用到- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute
方法
convenience initializer
則可以有很多個(gè),它可以選擇只初始化部分的 property
鼓拧。convenience initializer
最后到會(huì)調(diào)用到 designated initializer
半火,所以 designated initializer
也可以叫做 final initializer
NS_NOESCAPE修飾符
在masonry
源碼當(dāng)中,我們看到在修飾block
的時(shí)候用到了NS_NOESCAPE
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
NS_NOESCAPE
用于修飾方法中的block
類(lèi)型參數(shù)季俩,作用是告訴編譯器钮糖,這個(gè)block
在mas_makeConstraints:
方法返回之前就會(huì)執(zhí)行完畢,而不是被保存起來(lái)在之后的某個(gè)時(shí)候再執(zhí)行
masonry
為什么不會(huì)引起循環(huán)引用
比如我們可能經(jīng)常會(huì)寫(xiě)如下代碼:
[self.view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view.superview).offset(10);
}];
這里為什么不需要寫(xiě)@weakify(self)
,接著看mas_makeConstraints:
是如何實(shí)現(xiàn)的:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
從代碼中可以看到店归,block
強(qiáng)引用了self
,但是在mas_makeConstraints:
方法中self
并沒(méi)有直接或間接持有block
阎抒,而是直接調(diào)用block(constraintMaker)
,所以不會(huì)引起強(qiáng)引用
鏈?zhǔn)秸{(diào)用實(shí)戰(zhàn)應(yīng)用
在我們開(kāi)發(fā)過(guò)程中,我們會(huì)經(jīng)常用到UILabel
消痛,每次初始化都要設(shè)置一堆的屬性且叁,比較麻煩,當(dāng)然我們也可以采取類(lèi)似如下方法:+ (UILabel *)createLabelWithFont:(UIFont *)font andTextColor:(UIColor *)color andDefaultContent:(NSString *)content
秩伞,但是一旦我們所需要初始化的參數(shù)比較多時(shí)逞带,就會(huì)造成方法參數(shù)非常多,甚至我們有些參數(shù)根本不需要初始化稠歉,用鏈?zhǔn)骄幊淘撊绾螌?shí)現(xiàn)呢掰担??
- 首先為
UILabel
創(chuàng)建一個(gè)category
怒炸,#import "UILabel+zjLabel.h"
带饱,代碼如下:
#import "UILabel+zjLabel.h"
@implementation UILabel (zjLabel)
+ (UILabel *)zj_createLabel:(void (^)(UILabel * _Nonnull))block{
UILabel *label = [UILabel new];
block(label);
return label;
}
- (UILabel *(^)(NSString *))zj_text{
return ^(NSString *str){
self.text = str;
return self;
};
}
- (UILabel *(^)(UIFont *))zj_font{
return ^(UIFont *font){
self.font = font;
return self;
};
}
- (UILabel *(^)(UIColor *))zj_textColor{
return ^(UIColor *color){
self.textColor = color;
return self;
};
}
- (UILabel *(^)(NSTextAlignment))zj_textAlignment{
return ^(NSTextAlignment aligment){
self.textAlignment = aligment;
return self;
};
}
在需要的地方調(diào)用方式如下:
UILabel *label = [UILabel zj_createLabel:^(UILabel * _Nonnull label) {
label.zj_text(@"haha").zj_font([UIFont systemFontOfSize:24]).zj_textColor(UIColor.redColor);
}];
[superview addSubview:label];
不需要初始化的參數(shù)可以直接不寫(xiě),只初始化我們需要的
總結(jié)
另外很多人擔(dān)心自動(dòng)布局的性能問(wèn)題阅羹,事實(shí)上蘋(píng)果已經(jīng)在iOS12中對(duì)auto layout
進(jìn)行優(yōu)化:
WWDC2018講解了iOS12優(yōu)化后的表現(xiàn)
可以看到在iOS12之前auto layout
性能會(huì)隨著嵌套視圖的增加呈指數(shù)增長(zhǎng)勺疼,但是在iOS12上蘋(píng)果官方已經(jīng)對(duì)此進(jìn)行了優(yōu)化,隨著嵌套視圖的增加性能問(wèn)題得到了大幅的提升捏鱼。
鏈?zhǔn)骄幊痰奶攸c(diǎn):方法返回值是block
执庐,而且該block
必須有返回值,返回值就是對(duì)象本身导梆,block
也可以輸入?yún)?shù)
另外Masonry
框架分析部分轨淌,做了簡(jiǎn)單的分析,想要看詳細(xì)的參考下方鏈接看尼,作者寫(xiě)的太詳細(xì)了递鹉,我都不知道寫(xiě)啥了。藏斩。躏结。。狰域。
Refrence:
https://www.cnblogs.com/ludashi/archive/2016/07/11/5591572.html