本篇文章是筆者對上篇文章《關於 Masonry 的一些思考》的一些本身的解答,哪裏有理解不到位的地方,請盡情拍磚。若是想先看無答案版,請前往上篇文章 《看完 Masonry
源碼後的幾點思考?》。git
圖片來自戴銘文章 《讀 SnapKit 和 Masonry 自動佈局框架源碼》github
Masonry
都作了些什麼?Masonry
是一個讓開發者用簡潔優雅的語法來調用原生 AutoLayout
進行佈局的輕量級框架。Masonry
擁有本身的 DSL
佈局語言,讓咱們能夠更具象地描述約束的增長與更新,讓約束的代碼也變得更加簡潔易讀、容易理解。編程
DSL 是一種基於特定領域的語言,它使工做更貼近於客戶的理解,而不是實現自己,這樣有利於開發過程當中,全部參與人員使用同一種語言進行交流。簡單來講,就是咱們只需描述出咱們想要什麼效果,而毋需涉及底層實現,這無疑下降了工做過程當中溝通協調的門檻。swift
語言過於蒼白,讓咱們 show code:api
原生 AutoLayout
實現一個紅色 view 佈局數組
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 進行佈局:bash
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);
}];
複製代碼
代碼甚至能夠再精簡下:app
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
複製代碼
以上代碼來自 Masonry 的 github 介紹框架
通過上面的代碼比較,Masonry
語法的簡潔優雅效果是淺顯易見的。代碼不只變得精簡,並且閱讀成本也基本降到了最低。less
[self.view addSubview:btn];
[btn makeConstrants:^(MASLayoutConstraint *make){
make.left.equalTo(self.view).offset(12);
}];
複製代碼
答: 不會發生循環引用,方法中 block
參數雖然引用 self.view
,間接持有了 btn
,可是 block
參數是個匿名 block,而且在方法實現裏未額外引用這個 block
參數, block
並未被 btn
所持有,也就不存在二者相互持有、循環引用。block -> self.view -> btn -(未引用)- block
而上述方法定義中也明確使用 NS_NOESCAPE
修飾 block
參數, 這個修飾符代表 block
在方法執行完前就會被執行釋放,而不會對 block
進行額外的引用保存。
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block
在代碼中 Masonry
也確實是這麼作的:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
複製代碼
從上面代碼中,能夠清除地看到,block
參數在 return
以前就被執行,並未被其餘對象引用。
更多關於 NS_NOESCAPE 的介紹
額外拓展,不少同窗對
block
的循環引用都不太瞭解:一樣是匿名 block 參數,系統動畫
[UIView animateWithDuration:100 animations:^{
NSLog(@"%@",self);
}];
複製代碼
不會形成循環引用,而 MJRefresh
的 header
初始化方法
self.scrollView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
NSLog(@"%@", self);
}];
複製代碼
爲何會形成循環引用?
MJRefresh
的 headerWithRefreshingBlock:
方法內部,返回的 MJRefreshNormalHeader
對象強引用了這個 block
,而這個返回對象最後又被 self.scrollView.mj_header
強引用了,也就形成了 self -> scrollView -> mj_header -> block -> self
的強引用閉環,所以會形成循環引用。
headerWithRefreshingBlock:
實現代碼:
+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
{
MJRefreshHeader *cmp = [[self alloc] init];
cmp.refreshingBlock = refreshingBlock;
return cmp;
}
複製代碼
系統的動畫實現方法中,self
並未和這個 block
產生關聯,但 block
確實持有了 self
,但筆者猜想 block
對self 並非強引用,由於若是在這個動畫時間內控制器執行 POP
操做,self
會當即被釋放掉,也就是說除了導航控制器棧,self
並未被額外的強引用,不然 self 不會被釋放。
self 未引用 block (弱)-> self 。所以也不存在循環引用
想想,當方法中使用匿名
block
、匿名對象做爲參數,這些匿名對象是被誰持有?會在何時釋放呢?歡迎在評論中探討。
MAS_SHORTHAND
、MAS_SHORTHAND_GLOBALS
宏是作什麼用的?它的效果是如何實現的呢?MAS_SHORTHAND
宏能夠在調用 Masonry
api
的時候省去 mas_
前綴
Masonry
爲 View
定義了 一個 View+MASAdditions
分類。在這個分類中,全部的成員屬性和方法都是帶有 mas_
前綴的。Masonry 還另外定義了 View+MASShorthandAdditions
分類,在這個分類中全部的全部屬性和成員變量都不帶 mas_ 前綴。但這個分類被 #ifdef MAS_SHORTHAND #endif
所包裹。 效果以下:
//MASShorthandAdditions 分類
#ifdef MAS_SHORTHAND
...
...(不帶有 mas_ 前綴的成員變量和方法)
...
#endif
複製代碼
這樣只有定義了 MAS_SHORTHAND
以後這個分類纔會被編譯,而這個分類內部全部屬性的 get
方法、對外的接口方法實現仍是調用的帶有 mas_
前綴的方法,對於咱們開發者來講,只是在 mas_
屬性與方法外面包裹上了一層語法糖。
不帶有 mas_
前綴方法的實現:
//屬性的 get 方法宏,在屬性前拼接 mas_ 前綴,調用帶有前綴的屬性 get 方法
#define MAS_ATTR_FORWARD(attr) \
- (MASViewAttribute *)attr { \
return [self mas_##attr]; \
}
複製代碼
//不帶有 mas_ 前綴的 API,內部會調用帶有 mas_ 前綴的 API
- (NSArray *)makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
return [self mas_makeConstraints:block];
}
複製代碼
而MAS_SHORTHAND_GLOBALS
宏會將 equalTo()
、 greaterThanOrEqualTo()
、 offset()
宏定義爲 mas_equalTo()
、 mas_greaterThanOrEqualTo()
、 mas_offset()
。
而帶有 mas_
前綴的方法會將括號內的 block
參數從基本數據類型轉化爲 NSValue
對象類型
#define mas_equalTo(...) equalTo(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
複製代碼
Masonry
的 makeConstraints:
、updateConstraints:
、 remakeConstraints:
有什麼區別,分別適合那些場景?- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
複製代碼
remakeConstraints:
和上面代碼的惟一區別就是增長了constraintMaker.removeExisting = YES;
當 [constraintMaker install]
時,若是 removeExisting
判斷爲 true
,會將已安裝的約束所有執行 [constraint uninstall]
卸載;
而 updateConstraints:
和上面代碼的惟一區別就是在調用 block
以前增長了一句 constraintMaker.updateExisting = YES
標示。
當 [constraint install]
執行時,會判斷 updateExisting
的值, 若是爲 true
會接着判斷約束和已安裝的約束是否類似,(判斷是否類似的規則是,兩條約束只有 constant
常量值不同,其它諸如 firstItem
secondItem
firstAttribute
secondAttribute
relation
multiplier
priority
必須和以前約束徹底一致,才爲類似約束。),若是存在類似約束,則進行約束更新,不然就新增這條約束。所以咱們要十分注意 updateConstraints:
新更新的約束會不會和已有的約束衝突, 例如當咱們以前約束爲 make.right.equalTo(self.view).offset(-12);
更新後爲 make.right.equalTo(self.view.mas_centerX).offset(-15);
這是兩條不類似的約束(secondAttribute
不同),若是更新約束,會形成約束衝突。
make.left.right.top.equalTo(self.view).offset(0)
都作了些什麼?make.left
生成並返回 MASViewConstraint
對象,須要注意的是:
該對象已保存了調用 view
(FirstView
) 和 left
(FirstAttribute
)
該對象已被添加到 make
的 constraints
數組內保存
MASViewConstraint.right
生成並返回了 MASCompositeConstraint
對象,須要注意的是:
MASCompositeConstraint
對象保存了包含 left
和 top
的兩條 MASViewConstraint
對象make
的 constraints
數組以前保存的 MASViewConstraint
對象被替換爲該 MASCompositeConstraint
對象MASCompositeConstraint.top
返回以前的 MASCompositeConstraint
對象,須要注意的是:
MASCompositeConstraint
增長了一條 top
約束MASCompositeConstraint .equalTo(self.view)
返回以前的 MASCompositeConstraint
,
MASCompositeConstraint
保存的幾條約束, 爲他們設置 layoutRelation
、secondView
和 secondAttribute
equalTo()
參數是 view
類型,secondAttribute
依舊是 nil
,會在最後約束安裝時若是判斷爲 nil
則值初始化爲 FirstAttribute
MASCompositeConstraint.offset
無返回值
MASCompositeConstraint
保存的幾條約束,爲他們設置 layoutConstant
最後約束安裝時 執行 [constraintMaker install]
; 就會根據 firstView
FirstAttribute
layoutRelation
secondView
secondAttribute
layoutConstant
來生成原生的約束 NSLayoutConstraint
,並將原生約束添加到 firstView
secondView
最近的公共父視圖上生效。
Masonry
是如何作到鏈式優雅調用的?鏈式編程思想:簡單來講,是將多個操做(多行代碼)經過點號(.)連接在一塊兒成爲一句代碼,使代碼可讀性好。a(1).b(2).c(3)
鏈式編程特色:方法的返回值是 block , block 必須有返回值(自己對象),block 參數就是須要操做的值。
那 make.left.right.top.bottom.equalTo(self.view).offset(12)
鏈式調用的具體過程是什麼樣的呢?
首先 Masonry
定義了一個 MASConstraint
抽象類 上面全部的方法返回值都是 MASConstraint
類型,而全部的調用者除了第一個爲 MASConstraintMake
類型,其它都是 MASConstraint
類型調用。因此前一個方法的返回值正好做爲下一個方法的調用者,而調用過的全部方法修改的約束都被 maker
的 constraints
所記錄下來。隨後在 [constraintMaker install]
; 的時候遍歷 constraints
執行 [constraint install]
MASViewConstraint
爲何要弱引用一個 MASLayoutConstraint 的實例對象,它又用這個對象作了什麼?Masonry
庫最後都會生成一個 MASVIewConstraint
對象,Masonry
會根據這個對象生成系統原生 NSLayoutConstraint
約束的建立,然後期可能要對這個原生約束進行一些移除操做。須要記錄這個原生約束對象。
MASConstraintMaker
持有一個 constraints
數組, 而 MASViewConstrint
類也有一個用來記錄約束的數組,這兩個數組都是用來記錄生成的約束,那這兩個數組有什麼區別嗎?各自的做用又是什麼?MASConstraintMaker
的 數組是記錄 本次 Masonry
API
調用生成的約束,最後 make
將這個數組內的約束遍歷安裝 install
。 數組裏存儲的是 MASViewConstraint
和 MASCompositeConstraint
對象
而 MASViewConstrint
類的數組,記錄的是 Masonry
調用者 View
已經安裝了哪些約束,這個數組在後期調用者調用 updateConstraints:
時判斷,更新的約束是否已經安裝了 ,remakeConstraints:
方法時,須要根據數組將已經安裝過的約束移除。數組裏存儲的都是 MASViewConstrint
對象。
儘管筆者水平有限,但對這些問題的拙劣看法仍是奉上,但願能夠給讀 Masonry
源碼的小夥伴帶來些不同的視角,若是對於文中有解讀不當的地方也請您不吝指出。