Masonry源碼解讀

Masonry這個框架是使用代碼進行自動佈局使用的,它的使用很是普遍,這段時間一直在學習這個框架,所以想把學到的東西記下來,方便之後查閱,也便於與人分享。數組

自動佈局約束的等式:bash

item1.attribute1 = multiplier × item2.attribute2 + constant

Masonry中使用了大量的點鏈式語法,考慮到應該有些小夥伴不知道點鏈式語法的前因後果,所以這裏先整理一下點鏈式語法app

點鏈式語法

咱們先來看一下Masonry框架的一種使用:框架

[view mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(superview.mas_left).mas_offset(30);
    }];
複製代碼

上面的代碼是Masonry的簡單的使用,這裏面就用到了點鏈式語法make.left.equalTo(superview.mas_left).mas_offset(30);,咱們看一下這句點鏈式語法,這裏麪包括三個要素:less

  • 點語法:咱們在訪問屬性的時候會使用點語法。
  • 小括號調用:在Objective-C中使用[ ]來調用方法,只有在調用Block的時候會使用(),所以這裏咱們可使用Block來實現點鏈式語法中的()。
  • 連續調用:Block是有返回值的,那麼咱們能夠在每次調用完Block後返回調用者對象自己,那麼咱們就能夠實現連續的調用了。 總結起來就是:

咱們能夠聲明一些Block類型的屬性,讓block類型的屬性的返回值爲其自己。ide

下面用一個計算器的例子來講明一下:佈局

//Calculator.h
@interface Calculator : NSObject

//這裏是建立一個屬性,屬性的類型是block類型,屬性名是add
@property (nonatomic, copy)Calculator * (^add)(NSInteger num);
@property (nonatomic, copy)Calculator * (^minus)(NSInteger num);
@property (nonatomic, copy)Calculator * (^multiply)(NSInteger num);
@property (nonatomic, copy)Calculator * (^divide)(NSInteger num);

@property (nonatomic, assign)NSInteger result;

@end
複製代碼
//Calculator.m
@implementation Calculator

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.result = 0;
    }
    
    return self;
}

//這裏實現的是add這個屬性的get方法,只不過屬性的類型是block類型的。
- (Calculator * (^)(NSInteger num))add
{
    return ^id(NSInteger num){
        self.result += num;
        return self;
    };
}

- (Calculator * (^)(NSInteger num))minus
{
    return ^id(NSInteger num){
        self.result -= num;
        return self;
    };
}

- (Calculator * (^)(NSInteger num))multiply
{
    return ^id(NSInteger num){
        self.result *= num;
        return self;
    };
}

- (Calculator * (^)(NSInteger num))divide
{
    return ^id(NSInteger num){
        self.result /= num;
        return self;
    };
}

@end
複製代碼

調用:學習

Calculator *calculator = [[Calculator alloc] init];
    calculator.add(5).minus(8).multiply(8).divide(23);
複製代碼
  • 1.calculator.add是調用了add屬性的get方法,這個方法會返回一個block,block以下:
return ^id(NSInteger num){
        self.result += num;
        return self;
    };
複製代碼
  • 2.calculator.add(5)會執行這個block,這個block的返回值是Calculator對象自己,因此calculator.add(5)執行完了獲得的是一個Calculator對象。
  • 3.Calculator對象繼續訪問minus屬性,執行minus屬性的get方法。 #####更簡潔的實現 上面是經過聲明一系列的block類型的屬性,再實現block屬性的get方法來實現鏈式調用,可是Masonry的實現方式和這種方式仍是有區別,咱們在Masonry中並無發現Block類型的屬性的聲明,反而是看到了一些平時見的比較少的方法的聲明:
    0D223676-9F5F-4D4E-AAE0-05FA01F25A5F.png
    也就是說Masonry中是把Block類型的屬性改爲了返回值爲Block類型的方法,這樣也能成功實現鏈式語法,這是爲何呢? 回想一下,當咱們經過點語法去訪問屬性的時候實質上就是訪問了get方法,那麼當不存在一個名爲name的屬性時,咱們使用self.name去訪問時是否是也會跑去執行名爲name的方法呢?答案是確定的,也就是隻要咱們申明瞭一個xxx方法,那就能夠放心的寫self.xxx。 因此最終Calculator.h文件就改爲了這樣:
@interface Calculator : NSObject
/*
@property (nonatomic, copy)Calculator * (^add)(NSInteger num);
@property (nonatomic, copy)Calculator * (^minus)(NSInteger num);
@property (nonatomic, copy)Calculator * (^multiply)(NSInteger num);
@property (nonatomic, copy)Calculator * (^divide)(NSInteger num);
@property (nonatomic, assign)NSInteger result;
*/
- (Calculator * (^)(NSInteger num))add;
- (Calculator * (^)(NSInteger num))minus;
- (Calculator * (^)(NSInteger num))multiply;
- (Calculator * (^)(NSInteger num))divide;

@end
複製代碼

Masonry的使用方法

1.使用MASConstraintMaker建立約束
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);
}];
複製代碼

或者更簡單的方法:動畫

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(superview).with.insets(padding);
}];
複製代碼
並非只有equalTo即等於這一種關係,還能夠有:

lessThanOrEqualTo:等同於NSLayoutRelationLessThanOrEqual greaterThanOrEqualTo:等同於NSLayoutRelationGreaterThanOrEqualui

2.MASViewAttribute

Masonry中有MASViewAttribute這個類,這個類就等同於NSLayoutAttribute這個類:

MASViewAttribute NSLayoutAttribute
view.mas_left NSLayoutAttributeLeft
view.mas_right NSLayoutAttributeRight
view.mas_top NSLayoutAttributeTop
view.mas_bottom NSLayoutAttributeBottom
view.mas_leading NSLayoutAttributeLeading
view.mas_trailing NSLayoutAttributeTrailing
view.mas_width NSLayoutAttributeWidth
view.mas_height NSLayoutAttributeHeight
view.mas_centerX NSLayoutAttributeCenterX
view.mas_centerY NSLayoutAttributeCenterY
view.mas_baseline NSLayoutAttributeBaseline
3.與常數有關的問題

自動佈局不容許對齊的屬性如left,right,centerY等設置爲常數,若是咱們傳了一個常數給這些屬性,Masonry會自動把這些約束變爲相對於父視圖的約束,即:

//creates view.left = view.superview.left + 10
make.left.equalTo(@10)
複製代碼
4.mas前綴相關

在使用Masonry的時候,有時候會比較迷糊何時使用帶有mas前綴的,何時使用不帶前綴的,咱們看下面這句代碼:

make.top.mas_equalTo(42);
複製代碼

這句代碼也能夠這樣寫:

make.top.equalTo(@42);
複製代碼

可是這樣寫就會報錯:

make.top.equalTo(42);
複製代碼

緣由就在於這個括號裏面的參數類型必須是id類型,若是括號裏面的參數不傳id類型就傳常量類型也行,那麼就必需要在equalTo前面加上mas,加上mas後,mas_equalTo會把傳進來的數值類型變成id類型。

5.MASCompositeConstraint類相關

Masonry給了咱們幾個便利的方法來讓咱們一次性建立多個約束,Masonry中與這個約束相關的類是MASCompositeConstraint類,簡單使用以下: edges

// make top, left, bottom, right equal view2
make.edges.equalTo(view2);

// make top = superview.top + 5, left = superview.left + 10,
//      bottom = superview.bottom - 15, right = superview.right - 20
make.edges.equalTo(superview).insets(UIEdgeInsetsMake(5, 10, 15, 20))
複製代碼

size

// make width and height greater than or equal to titleLabel
make.size.greaterThanOrEqualTo(titleLabel)

// make width = superview.width + 100, height = superview.height - 50
make.size.equalTo(superview).sizeOffset(CGSizeMake(100, -50))
複製代碼

center

// make centerX and centerY = button1
make.center.equalTo(button1)

// make centerX = superview.centerX - 5, centerY = superview.centerY + 10
make.center.equalTo(superview).centerOffset(CGPointMake(-5, 10))
複製代碼
6.修改已經存在的約束

當咱們只是修改約束的constant的時候,可使用mas_updateConstraints:

// this is Apple's recommended place for adding/updating constraints // this method can get called multiple times in response to setNeedsUpdateConstraints // which can be called by UIKit internally or in your code if you need to trigger an update to your constraints - (void)updateConstraints { [self.growingButton mas_updateConstraints:^(MASConstraintMaker *make) { make.center.equalTo(self); make.width.equalTo(@(self.buttonSize.width)).priorityLow(); make.height.equalTo(@(self.buttonSize.height)).priorityLow(); make.width.lessThanOrEqualTo(self); make.height.lessThanOrEqualTo(self); }]; //according to apple super should be called at end of method [super updateConstraints]; } 複製代碼

當咱們要修改的不止是約束的constant的時候,使用mas_updateConstraints就力不從心了,這時就須要使用mas_remakeConstraints:

- (void)changeButtonPosition {
    [self.button mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.size.equalTo(self.buttonSize);

        if (topLeft) {
        	make.top.and.left.offset(10);
        } else {
        	make.bottom.and.right.offset(-10);
        }
    }];
}
複製代碼

休息一下

解讀源碼

咱們在解讀源碼的時候先從最簡單最基礎的使用開始,而後由淺入深,逐漸深刻。下面咱們先分析一下整個框架的文件結構:

E4C13061-A282-4C44-9626-B797EE011C74.png
下面就從一個最簡單最基本的使用開始來探究源碼:

[view mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(superview.mas_left).mas_offset(30);
    }];
複製代碼

咱們先無論外面的方法調用,只須要知道make是MASConstraintMaker類型的就好了,從make.left.equalTo(superview.mas_left).mas_offset(30);開始:

  • 1.make.left left是它的一個屬性,這裏調用的是屬性的getter方法。
    57BE757D-EE05-47E3-9F2D-4A58A3F2712A.png
    繼續往下查看:
    668FACAD-700E-4583-82AB-867A76F46A5D.png
    再繼續:
    20ED5488-141A-442A-A8D5-3A7798A4486F.png
    咱們來看一下MASViewConstraint對象的建立:
    2728C00E-9170-4905-BCB0-82CD9C91ACAF.png

那麼如今咱們來總結一下make.left作了哪些事:

make.left是調用了MASConstraintMaker類的left屬性的get方法,這裏建立了一個MASViewAttribute對象,這個對象由一個UIView對象和一個NSLayoutAttribute來建立,這裏UIView對象是view,NALayoutAttribute爲NSLayoutAttributeLeft,因此這裏MASViewAttribute對象也就是封裝了約束等號左邊的兩個元素。。而後使用建立的MASViewAttribute對象來建立了一個MASViewConstraint對象,這個對象表明這一行代碼所表示的整個約束。最終make.left返回了一個MASVIewConstraint對象。

須要注意的是,MASConstraintMaker對象有一個數組類型的consrtaints屬性,新建立的MASViewConstraint對象被加入到了這個屬性中,在最後添加約束的時候會遍歷這個數組。

  • 2.superview.mas_left mas_left是分類的一個屬性,因此superview.mas_left會調用分類的-(MASViewAttribute *)mas_left方法。
    9413264B-AB4F-4F1B-921A-153DE3B96E93.png
    這裏經過代碼建立了一個MASViewAttribute對象,對象的view即superview,對象的attribute即NSLayoutAttributeLeft,咱們看看是如何建立的:
    892F2158-3377-4352-8EFF-B5937A412744.png
    總結一下:

superview.mas_left返回了一個MASViewAttribute對象,這個對象封裝了約束等號右邊的兩個元素。

  • 3.make.left.equalTo(superview.mas_left) 進入equalTo查看具體實現:
    F8189FD9-D511-48B3-95B5-CBA04C2DE78C.png
    也就是說我執行make.left.equalTo會獲得一個Block,那麼我執行make.left.equalTo(superview.mas_left)就是執行這個Block,即make.left.equalTo(superview.mas_left)會執行self.equalToWithRelation(attribute, NSLayoutRelationEqual)這一行核心代碼,並返回這一行核心代碼的返回值。 因爲make.left是MASViewConstraint對象,因此咱們要去MASViewConstraint類中查看equalWithRelation的實現:
    4D84193E-0A50-425E-8CF8-297CCB1B53B8.png
    self.secondViewAttribute = attribute;會觸發secondViewAttribute這個屬性的set方法,咱們看一下其set方法的實現:
    1C0A9CAA-0874-49C3-9E93-F126A4428219.png
    這裏的意思就是make.left.equalTo()這個括號裏面傳入的東西可能有三種狀況,第一種是數字,第二種是一個UIView對象,若是是UIView對象,那就將其layoutAttribute設置爲什麼firstAttribute一致,也就是咱們也能夠這樣寫:make.left.equalTo(superview),這樣Masonry也能成功識別。第三種是傳入的MASViewConstraint對象,若是傳入這種對象則能夠直接賦值給secondViewAttribute屬性。 下面咱們再來看一下第一種狀況傳入數字的處理方式,咱們進入setLayoutConstantWithValue:這個方法:
    B7C31203-51F7-4E48-80ED-4F3B58AC9D31.png
    代碼裏面設置offset,centerOffset等都不是真正的實現,真正的實如今offset屬性的set方法裏,咱們要其MASViewConstraint類中找offset屬性的set方法:
    C66A8756-CD7D-418C-838F-4130A72E8A0E.png
    總結起來就是若是傳入的是數值類型,那麼就給MASViewConstraint的layoutConstant屬性賦值。

這樣咱們就清楚了make.left.equalTo()這個括號中傳入各類不一樣類型的值會怎麼操做。

總結一下make.left.equalTo(superview.mas_left)作的事情:

make.left建立了一個firstViewAttribute,firstViewAttribute的view屬性即爲 mas_makeConstraint方法的調用者,其layoutAttribute屬性爲NSLayoutAttributeLeftfirstViewAttribute封裝了約束等式左邊的兩個item。接着經過傳入firstViewAttribute建立了一個MASViewConstraint對象。superview.mas_left則是建立了一個secondViewAttribute對象,該對象的view即爲superview,layoutAttributeNSLayoutAttributeLeftmake.left.equalTo(supervie.mas_left)則是將secondViewAttribute賦值給MASViewConstraint對象的secondViewAttribute屬性,並給MASViewConstraint對象的layoutRelation屬性賦值。

  • 4..mas_offset(30) mas_offset的顏色是土黃色,說明這是一個宏定義,咱們點進去,發現這個宏定義是定義在MASConstraint.h文件中:
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
複製代碼

這不是一個簡單的宏定義,裏面還進行了嵌套,咱們看一下MASBoxValue()方法作了什麼,在MASUtilities.h這個文件中找到了MASBoxValue()這個方法:

AF2FEF9E-81D3-4CAC-A6AB-79BBED81485C.png
從圖中咱們能夠看到,這個方法就是把一些數值類型的值轉爲id類型。

Masonry的用法這部分我說過mas前綴的使用,這裏咱們就看到了其實現方法。若是咱們括號裏想要直接傳入數值類型而不是id類型的參數,那麼前面使用的API就必須帶mas前綴,不然報錯,若是傳入的是id類型,則前面使用的API是否帶mas前綴都可。

因此.mas_offset(30)也就等於.valueOffset(@30),那麼咱們來查看一下MASConstraint.m中的實現:

9EDB3756-844B-48C8-BF55-1DF70D10A560.png
最終就是設置了 MASViewConstraint的layoutConstant屬性:
85F60147-C2E5-4A0B-9AB7-E96B8BD51130.png
到這裏 make.left.equalTo(superview.mas_left).mas_offset(30);這行代碼就全解讀完了。總結一下就是:

make,left建立了一個MASViewConstraint對象,爲這個對象的firstViewAttribute屬性賦值,superview.mas_left即建立了一個MASViewAttribute對象,equalTo()即把這個MASViewAttribute對象賦值給MASViewConstraint對象的secondViewAttribute屬性,.mas_offset(30)則是給MASViewConstraint對象的layoutConstant屬性賦值爲30.

  • 5.mas_makeConstraints: mas_makeConstraints:這個方法是在UIView的分類中定義並實現的,下面咱們看一下其具體實現:
    48B3AC46-BE1A-4FEE-98E2-252900F394F3.png
    72B1DEFF-9089-41C5-A215-C070C0781C33.png
    再來看[constraint install]:,這個方法的內容比較多,咱們分兩部分來看:
    0735EF28-E952-4C4F-8F20-7DA742D42D64.png
    C50DD5C3-64C0-4E40-86F2-B18311F7979C.png
    到這裏約束就添加完成了。 總結一下添加約束的大致流程:

首先根據MASViewConstraint對象的firstViewAttributesecondViewAttribute這兩個屬性,訪問這兩個屬性獲得firstLayoutItemfirstLayoutAttributesecondLayoutitemsecondLayoutAttribute。而後處理有時是對齊屬性如left沒有提供view的狀況,這時就要設置view爲其父視圖。而後尋找應該將約束添加到哪一個視圖上,最後添加約束到對應的視圖上。

休息一下

組合約束(MASCompositeConstraint)

Masonry中能夠直接約束size,center,edge這樣的組合約束。其本質也就是把它拆成多個單個約束,好比對size的約束,就是拆成width和height這兩個約束。下面咱們看一下其具體的實現方法:

make.size.equalTo(superview).sizeOffset(CGSizeMake(10, 10));
複製代碼
  • 1.make.size
    44B0B9CD-B912-44A6-8C8B-F07275FDFAE9.png
    繼續往下看:- (MASConstraint *)addConstraintWithAttributes:(MASAttribute)attrs方法:
    081C430A-BD90-4FC8-9D62-4FDC07D0EA6B.png
    總結一下make.size作了哪些事:

把size這一個拆分紅了width和height這兩個,根據這兩個約束建立了兩個MASViewConstraint對象,裝到一個數組裏面,使用這個數組來建立了一個MASCompositeConstraint對象,最後返回這個MASCompositeConstraint對象。

  • 2..equalTo(superview)
    D5C22579-A50F-44BC-A79B-5815FB5FC96D.png
    繼續:
    5FDF1EB1-ED9A-4AD4-8EA8-B935A7AF5D5F.png
    總結一下make.size.equalTo(superview):

把size這一個約束拆分紅了width和height這兩個約束,並根據此建立了兩個MASViewConstraint對象,根據這兩個對象組成的數組去建立一個MASCompositeConstraint對象。而後遍歷MASCompositeConstraint對象的childConstraints數組,取出數組裏面的額每一個MASViewConstraint對象,而後像處理單個MASViewConstraint同樣去處理。

  • 3..sizeOffset(CGSizeMake(10, 10)) 能夠想象就是把它拆分,而後賦值給兩個MASViewConstraint的layoutConstant屬性,就不詳細說了。

mas_updateConstraints:和mas_remakeConstraints:

自動佈局約束等式: item1.attribute1 = multiplier × item2.attribute2 + constant

有時候咱們有更改約束的需求,好比咱們要作一個動畫,移動某個視圖,那就須要改變視圖約束,當咱們只是改變約束的constant時,可使用mas_updateConstraints:這個方法。而當咱們須要改變的不止constant時,就須要調用mas_remakeConstraints:這個方法了。

先來看一下mas_updateConstraints:這個方法是怎麼實現只改變約束的constant的:

CEDE51E1-CB87-42C7-9E40-A3AFA01A4755.png
進入 [constraintMaker install]
99A0DCCC-96D2-455F-B0E2-EE71B5745FD0.png
看看 [constraint install]中是怎麼實現的:
2CE11985-5A8F-4701-939C-AB16EA8B56EA.png

總結起來,mas_updateConstraints:就是去self.installedView.constarints這個屬性數組中去遍歷,看有沒有這樣一個NSLayoutConstraint對象,它除了constant外,全部內容都和當前的NSLayoutConstraint對象一致,若是有這個對象,那麼就把該對象的constant改成當前NSLayoutConstraint對象的constant。這樣來完成約束條件的更新。

再來看一下mas_remakeConstraints: 當咱們要改變的約束條件不止是constant這麼簡單時,使用mas_remakeConstraints:就不頂用了,就要使用mas_remakeConstraints:這個方法。

F6106353-8D44-4F85-BE0C-1E8E8CE7D9A0.png
再看 [constraintMaker install]:
5D7151E9-43FE-4E11-8AB2-CE965A40B190.png
7AAA699D-C641-48AD-83D8-DA855ADE7437.png

這篇文章在簡書的地址:www.jianshu.com/p/8990c5a98…

相關文章
相關標籤/搜索