[Masonry] 實(shí)現(xiàn)原理及鏈?zhǔn)秸{(diào)用分析

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
masonry_struct_chart

從調(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ì)UIViewNSLayoutAttribute的封裝
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 initializerdesignated 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方法莲镣,Appleinit 方法分為兩種類(lèi)型:designated initializerconvenience 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è)blockmas_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)

auto_layout_ optimize

可以看到在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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末媳拴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子兆览,更是在濱河造成了極大的恐慌屈溉,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抬探,死亡現(xiàn)場(chǎng)離奇詭異语婴,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)砰左,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)匿醒,“玉大人,你說(shuō)我怎么就攤上這事缠导×幔” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵僻造,是天一觀的道長(zhǎng)憋他。 經(jīng)常有香客問(wèn)我,道長(zhǎng)髓削,這世上最難降的妖魔是什么竹挡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮立膛,結(jié)果婚禮上揪罕,老公的妹妹穿的比我還像新娘。我一直安慰自己宝泵,他們只是感情好好啰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著儿奶,像睡著了一般框往。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闯捎,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天椰弊,我揣著相機(jī)與錄音,去河邊找鬼瓤鼻。 笑死男应,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的娱仔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼游桩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼牲迫!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起借卧,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盹憎,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后铐刘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體陪每,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了檩禾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挂签。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖盼产,靈堂內(nèi)的尸體忽然破棺而出饵婆,到底是詐尸還是另有隱情,我是刑警寧澤戏售,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布侨核,位于F島的核電站,受9級(jí)特大地震影響灌灾,放射性物質(zhì)發(fā)生泄漏搓译。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一锋喜、第九天 我趴在偏房一處隱蔽的房頂上張望些己。 院中可真熱鬧,春花似錦跑芳、人聲如沸轴总。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)怀樟。三九已至,卻和暖如春盆佣,著一層夾襖步出監(jiān)牢的瞬間往堡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工共耍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虑灰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓痹兜,卻偏偏與公主長(zhǎng)得像穆咐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子字旭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容