Masonry框架源碼分析
相信大多數(shù)iOS開發(fā)者對Masonry框架并不陌生 , 本文是筆者通讀Masonry的代碼之后的一篇總結(jié), 也希望可以幫助大家更好的理解該框架. 怎奈筆者才疏學淺, 如有遺漏或錯誤也歡迎大家評論區(qū)指出, 大家一起進步!
iOS布局的演進
在說Masonry, 先簡單介紹一下iOS開發(fā)屏幕適配的發(fā)展過程. 在iPhone3Gs/4/4s時代, 手機屏幕尺寸都是一樣的, 對于開發(fā)者來說基本不用適配,彼時的屏幕布局基本都是采用frame, 但是隨著iPad的出現(xiàn), frame布局便不能滿足需求, 蘋果開始推出AutoResizing布局, 這種布局核心內(nèi)容就是: 以父容器為參照物來對子空間進行frame布局, frame不再是直接寫死的值, 而是可以根據(jù)父視圖的大小變化, 但是這種布局方式的缺點也很明顯, 就是不能設置兄弟視圖之間的關系, 所有蘋果煞費苦心的推出了AutoLayout, AutoLayout的出現(xiàn)基本彌補了AutoResizeing不足, iOS開發(fā)的屏幕適配變得更加輕松.
蘋果原生AutoLayout布局與Masonry比較
雖然蘋果的初衷很好, 但是無奈蘋果的NSLayoutConstraint布局實在是太過于臃腫了, 所以在github開始涌現(xiàn)出各種各樣的三方布局框架, 其中就有今天的主角Masonry. 筆者截取了部分布局代碼大家感受一下
UIView *blueView = [[UIView alloc]init];
blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:blueView];
///原生自動布局方式
//去掉aotoReszing
blueView.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
NSLayoutConstraint *left= [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0];
NSLayoutConstraint *right = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant: 0];
NSLayoutConstraint *bottom = [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:0];
[self.view addConstraint:top];
[self.view addConstraint:left];
[self.view addConstraint:right];
[self.view addConstraint:bottom];
上面只是對一個空間進行的代碼, 而對于iOSAPP來說, 每個頁面數(shù)個或數(shù)十個View都是很常見的事, 如果采用這種布局方法, 估計整個類里面全都是布局代碼了, 這必然會給代碼的閱讀與維護帶來很大的不便.
其實蘋果還有一種自動布局的方式相對與上面的布局方法稍微好一點, 那就是VFL布局, 感興趣的可以去了解一下, 這里貼上我之前總結(jié)的一個帖子 iOS開發(fā)之VFL布局總結(jié)
然后大家看一下使用Masonry實現(xiàn)上面同樣的功能的代碼
UIView *blueView = [[UIView alloc]init];
blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:blueView];
//Masonry布局實現(xiàn)
[v mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.bottom.right.equalTo(self.view);,
}];
綜合以上兩個示例對比, 高下立見, 使用Masonry布局代碼簡潔美觀, 使用原生布局代碼臃腫不堪,所以如果你是使用代碼來進行自動布局, 有什么理由不用Masonry呢!
Masonry源碼分析(正題)
Masonry框架整體來說并不是一種新的布局方式, 它僅僅是對NSLayoutConstraint做了一層封裝, 所以對于框架背后必然還是要進行一堆原生的代碼操作, 所以我們才需要進行一窺究竟!
我們按照調(diào)用順序來介紹來一一介紹
1. View+MASAdditions
View+MASAdditions 此文件看起來內(nèi)容很多, 但是仔細觀察, 其實主要分為兩部分, 第一部分就是給View擴展屬性(mas_left, mas_right, 等屬性), 第二部分就是給View擴展方法( mas_makeConstraints: 等方法), 為了便于分析文件精簡如下
@interface MAS_VIEW (MASAdditions)
@property (nonatomic, strong, readonly) MASViewAttribute *mas_left;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_xxx;
...
///設置約束
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
///更新約束
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
///重設余數(shù)
- (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
@end
由于后面三個方法實現(xiàn)幾乎一樣, 所以這里只對mas_makeConstraints方法進行簡單的分析
@implementation MAS_VIEW (MASAdditions)
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
...
@end
要使用AutoLayout第一件事就是要關閉當前View的translatesAutoresizingMaskIntoConstraints,
接著通過工廠類MASConstraintMaker生成了一個constraintMaker, 這個也就是我們在Block回調(diào)中調(diào)用make實例, 然后執(zhí)行Block代碼塊, 在代碼塊中make分別去執(zhí)行.top, .left等操作完成 最后執(zhí)行install操作;
2. MASConstraintMaker
在執(zhí)行block(constraintMaker) 實際上就是再執(zhí)行make.top.left.bottom.right.equalTo(self.view);
我們來看make.top的實現(xiàn)原理, 這里個也是Masonry巧妙的地方, 利用block的特性實現(xiàn)了鏈式調(diào)用.
//本類中l(wèi)eft, leading, top, width等調(diào)用的都是同一個方法 addConstraintWithLayoutAttribute
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
///此方法直接調(diào)用了(constraint:addConstraintWithLayoutAttribute:)
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
///添加約束到self.constraints數(shù)組中, 此處也是MASConstraintDelegate的代理實現(xiàn), 也用做本類的添加約束
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
//MASViewConstraint是MASConstraint子類
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
///判斷constraint是否是MASViewConstraint, 如果是組合MASCompositeConstraint屬性, 則說明使用者有調(diào)用錯誤, 因為組合屬性后面不應該在跟一個新屬性, 所以在這個方法里面過濾掉了組合屬性的問題
if ([constraint isKindOfClass:MASViewConstraint.class]) {//如果constraint不是第一次調(diào)用則他應該是一個組合約束了, make.top.left.right
//replace with composite constraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
//在添加單個屬性時constraint參數(shù)都為nil 所以以上兩個分支都忽略
///判斷constraint是否是nil;
if (!constraint) {//如果是第一次則添加一個新屬性進來
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
按照順序分析, 當調(diào)用make.top是最終會來到 - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute 方法中, 這個方法做了一下幾件事
第一件事生成viewAttribute屬性, 然后通過viewAttribute生成newConstraint這兩個實例所對應的類在后面介紹, 這里先標記上, 因為調(diào)用constraint參數(shù)傳遞的是nil 所以這里第一個if語句不執(zhí)行, 直接執(zhí)行第二個if分支里, 在第二個分支中,給新創(chuàng)建的newConstraint設置代理, 然后將newConstraint添加到maker所持有的數(shù)組中, 到這里第一個屬性top就完成了記錄.
整體來看就是調(diào)用top方法會生成一個top的約束, 然后將這個約束添加到maker所持有的數(shù)組constraints中
3. MASConstraint
MASConstraint實際上是一個抽象類, Masonry巧妙地使用了面向?qū)ο蟮亩鄳B(tài)特性進行編程. MASConstraint類中定義了很多抽象方法都需要在子類中實現(xiàn), 這里摘取幾個例子如下
#pragma mark - Abstract
//MASMethodNotImplemented() 這個宏定義采用了拋出錯誤的方法
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy { MASMethodNotImplemented(); }
- (MASConstraint * (^)(CGFloat divider))dividedBy { MASMethodNotImplemented(); }
- (MASConstraint * (^)(MASLayoutPriority priority))priority { MASMethodNotImplemented(); }
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { MASMethodNotImplemented(); }
- (MASConstraint * (^)(id key))key { MASMethodNotImplemented(); }
上面摘取的這些方法是MASConstraint中的實現(xiàn), 我們可以看到方法體都是直接調(diào)用了一個宏MASMethodNotImplemented(), 我們們順著這個宏發(fā)現(xiàn), 實際上就是一個拋出錯誤的處理, 如果子類不實現(xiàn)這個方法, 則調(diào)用時就會來到父類的這個方法中最終拋出錯誤, 間接達到java中抽象類的效果! 需要注意的是這個錯誤是運行時錯誤, 所以如果不調(diào)用依然是無法發(fā)現(xiàn)錯誤的.
這里我們依然使用 make.top.left.bottom.right.equalTo(self.view) 這個例子來將進行分析, 第2部分講到make.top最終是調(diào)用到MASConstraintMaker中的 - (MASConstraint )constraint:(MASConstraint )constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute 而make.top的返回值大家可以看到實際上是MASConstraint, 因此當make.top再接著調(diào)用.left的時候已經(jīng)變成了MASConstraint** 的實例進行.left的調(diào)用, 然后我們來看MASConstraint中的left方法(這里再強調(diào)一下MASConstraint 中的鏈式調(diào)用, 比如.left 實際上是走的left的Getter方法, OC的語法糖可以讓我們實現(xiàn)用點語法替代get方法, 而該方法返回值又是MASConstraint 類型, 所以可以實現(xiàn)鏈式調(diào)用)
@implementation MASConstraint
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MASConstraint *)top {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)right {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
- (MASConstraint *)bottom {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}
///這里是一個拋出錯誤的空實現(xiàn)
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
MASMethodNotImplemented();
}
@end
我們可以看到.left, .top等方法實際上最終都會調(diào)用 addConstraintWithLayoutAttribute方法, 而在MASConstraint中該方法實際上是一個抽象方法, 并無實質(zhì)的內(nèi)容實現(xiàn), 所以很明顯這個問題我們需要放到子類實現(xiàn)中來說了
-
MASViewConstraint
看頭文件實現(xiàn), 我們可以得知MASViewConstraint 繼承了 MASConstraint, 因此MASViewConstraint擁有父類的所有特性, 因為父類在上面已經(jīng)介紹過, 這里只說明一下MASViewConstraint 特有的東西
@interface MASViewConstraint : MASConstraint <NSCopying> ///第一個View的屬性 @property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute; ///第二個view的屬性 @property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute; ///構造方法 - (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute; @end
因為之前還未介紹MASViewAttribute, 所以這里先大概說一下, MASViewAttribute類實際上是約束的模型, 主要用來記錄約束內(nèi)的關系, 如果不太明白, 你可以看一下第4部分MASViewAttribute的介紹, 然后再回來看這個.
我們看到MASViewConstraint中有兩個 屬性firstViewAttribute 和 secondViewAttribute, 關于這兩個屬性我們來看一下原生的自動布局實現(xiàn)我們就明白了
NSLayoutConstraint *left= [NSLayoutConstraint constraintWithItem:blueView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0];
通過上面的代碼片段, 我們可以發(fā)現(xiàn), 約束是有兩部分組成的, 也即是第一個view的某個約束屬性, 和第二個View的某個約束屬性的關系, firstViewAttribute實際上就是用來存儲blueView和約束NSLayoutAttributeLeft secondViewAttribute 中記錄的是self.view和對應的NSLayoutAttributeLeft.
在描述MASConstraint 中提到.left/.top等這些方法實際上最終會調(diào)用到 - *(MASConstraint )addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute , 而這個方法是抽象方法,在MASConstraint中并沒有實際實現(xiàn), 所以我們接著說這個方法
@implementation MASViewConstraint #pragma mark - attribute chaining - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation"); //調(diào)用代理來完成屬性的添加 return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute]; } @end
我們可以看到MASViewConstraint中- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute, 直接調(diào)用了代理方法 constraint:addConstraintWithLayoutAttribute:, 看到這里可能稍微有點繞, 比如這個delegate是誰, 在哪里設置的這個delegate, 沒關系慢慢分析還是可以找到線索的, 我們再回到 make.top的最終調(diào)用, 如下
@implementation MASConstraintMaker ///添加約束到self.constraints數(shù)組中, 此處也是MASConstraintDelegate的代理實現(xiàn), 也用做本類的添加余數(shù) - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute]; //MASViewConstraint是MASConstraint子類 MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute]; ///判斷constraint是否是MASViewConstraint, 如果是組合MASCompositeConstraint屬性, 則說明使用者有調(diào)用錯誤, 因為組合屬性后面不應該在跟一個新屬性, 所以在這個方法里面過濾掉了組合屬性的問題 if ([constraint isKindOfClass:MASViewConstraint.class]) {//如果constraint不是第一次調(diào)用則他應該是一個組合約束了, make.top.left.right //replace with composite constraint NSArray *children = @[constraint, newConstraint]; MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children]; compositeConstraint.delegate = self; [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint]; return compositeConstraint; } //在添加單個屬性時constraint參數(shù)都為nil 所以以上兩個分支都忽略 ///判斷constraint是否是nil; if (!constraint) {//如果是第一次則添加一個新屬性進來 newConstraint.delegate = self; [self.constraints addObject:newConstraint]; } return newConstraint; } @end
仔細看, 在方法內(nèi)部最后一個if分支中正是給newConstraint設置代理的代碼, 也就是說 MASViewConstraint 的代理實際上就是MASConstraintMaker, 所以當make.top再去調(diào)用.left的時候, 實際上最終還會來到MASConstraintMaker中上面這個方法來添加屬性, 我們來一步一步的分析這個方法, 由于MASViewConstraint中 調(diào)用代理方法時第一個參數(shù)constraint并不為nil 所以, 上面這個方法調(diào)用會和在MASConstraintMaker中直接調(diào)用有所不同.
make.top的返回值是一個MASViewConstraint類型, 所以這里直接進入了第一個分支, 在第一個分支中創(chuàng)建了一個MASCompositeConstraint類型的實例(這個類接下來會分析), 然后return, 結(jié)束了方法調(diào)用!
?
-
MASCompositeConstraint
這個類是MASViewConstraint子類一個約束組合類, 作用就是把多個約束組合在一起, 當 make.left.top執(zhí)行結(jié)束后實際返回值類型是MASCompositeConstraint 接下來再接著執(zhí)行 make.left.top.bottom時, 實際上是執(zhí)行的MASViewConstraint中的bottom方法, 最終會調(diào)用子類MASCompositeConstraint 里面的 - (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute 方法, 我們來看這個方法的實現(xiàn)
@implementation MASCompositeConstraint - (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { id<MASConstraintDelegate> strongDelegate = self.delegate; //這里只是通過代理Maker中的方法獲取一個新約束, 然后添加到childConstraints數(shù)組中 MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute]; newConstraint.delegate = self; [self.childConstraints addObject:newConstraint]; return newConstraint; } @end
通過分析上面的代碼我們可以發(fā)現(xiàn), 最終還是要調(diào)用 MASConstraintMaker 中的添加約束的方法中, 這里就不再重復, 最后返回的依然是 MASCompositeConstraint類型的約束, 然后接著執(zhí)行 make.left.top.bottom.right 返回MASCompositeConstraint類型的實例;
接下來, 分析equalTo方法, 在父類MASConstraint中定義并實現(xiàn)了equalTo方法, 但是方法實現(xiàn)實際上調(diào)用的是equalToWithRelation方法, 而這個方法在MASConstraint類中做了一個空實現(xiàn), 并且要求子類分別實現(xiàn), 所以我們分別來看 MASViewConstraint 和 MASCompositeConstraint中的實現(xiàn)
@implementation MASViewConstraint
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attribute, NSLayoutRelation relation) {
if ([attribute isKindOfClass:NSArray.class]) {//TODO: 暫時還沒有找到多個屬性的調(diào)用, 有哪位看懂了這個分支, 可以評論區(qū)交流,
NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
NSMutableArray *children = NSMutableArray.new;
for (id attr in attribute) { //遍歷傳入屬性
//copy當前屬性,
MASViewConstraint *viewConstraint = [self copy];
viewConstraint.layoutRelation = relation;
viewConstraint.secondViewAttribute = attr;
///將多個copy的值加入數(shù)組
[children addObject:viewConstraint];
}
///創(chuàng)建組合屬性
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");
self.layoutRelation = relation;
//設置第二個屬性的值, 在secondViewAttribute的set方法中去設置值 attribute為id類型
self.secondViewAttribute = attribute;
return self;
}
};
}
@end
@implementation MASCompositeConstraint
#pragma mark - NSLayoutRelation proxy
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
return ^id(id attr, NSLayoutRelation relation) {
for (MASConstraint *constraint in self.childConstraints.copy) {
constraint.equalToWithRelation(attr, relation);
}
return self;
};
}
@end
觀察上面的兩個類中的equalToWithRelation方法的實現(xiàn), 我們可以發(fā)現(xiàn)MASCompositeConstraint 的實現(xiàn) 最終就是用自己持有的childConstraints中的各個constraint去掉用equalToWithRelation, 所以這里最終還是執(zhí)行到MASViewConstraint中去, 所以我們只需要看MASViewConstraint中equalToWithRelation的實現(xiàn)!
由于要實現(xiàn).equalTo(xxx)這樣的函數(shù)式編程, 所以equalToWithRelation內(nèi)部返回的是有個有參數(shù)的block, 這樣外部調(diào)用時就可以達到函數(shù)式編程的效果, block返回值是MASConstraint類型以達到鏈式調(diào)用的效果.
bolck內(nèi)部有個if分支 判斷條件是[attribute isKindOfClass:NSArray.class], 這里筆者還有一點疑惑, 暫時沒有找到什么時候會來到這個分支, 所以也請各位看官讀者指點迷津, 在分支的else語句里 執(zhí)行了self.secondViewAttribute = attribute; secondViewAttribute屬性前面已有描述這里不再贅述, 不過有一點需要著重說一下, 就是secondViewAttribute的Setter方法, 這里mas的作者巧妙的實現(xiàn)了Setter方法, 我們可以來做一下分析
///這個set方法比較特殊, 傳進來的并不是屬性的類型, 需要在set方法中轉(zhuǎn)換
- (void)setSecondViewAttribute:(id)secondViewAttribute {
if ([secondViewAttribute isKindOfClass:NSValue.class]) {///如果是NSValue則_secondViewAttribute直接取值
[self setLayoutConstantWithValue:secondViewAttribute];
} else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {///如果是UIView則取用和第一個View相同的值
_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);
}
}
在這個方法中對secondViewAttribute進行了三個種類判斷, 如下:
1.判斷是不是NSValue類型則說明是直接對第一個約束設置了常量, 例如: make.width.equalTo(@100);
2.判斷是不是MAS_VIEW(是一個宏, 在iOS開發(fā)時對應的是UIView), 如果是, 則創(chuàng)建一個secondViewAttribute, 而secondViewAttribute的layoutAttribute和firstViewAttribute的layoutAttribute,
3.判斷是不是MASViewAttribute, 如果是則直接設置給屬性secondViewAttribute;
綜合以上三點, 所以我們的equal()方法中可以是NSNumber, 也可以是view.mas_xxx, 也可以直接是一個view類型
通常調(diào)用Masonry還有一種形式是這樣的: make.left.equalTo(view).offset(10); 前面內(nèi)容都有分析, 現(xiàn)在獨看.offset()方法; 在MASConstraint類中offset定義依然是一個block屬性, 但是這里稍有不同, .offset()實質(zhì)上調(diào)用的是offset的getter方法如下:
- (MASConstraint * (^)(CGFloat))offset {
return ^id(CGFloat offset){
self.offset = offset;
return self;
};
}
看完以后可能會有點蒙, 尤其是block內(nèi)部的實現(xiàn) self.offset = offset, 為何一個CGFloat的值可以賦值給block類型的屬性呢? 這里得說一下, 實際上 self.offset = offset中的self.offset是在調(diào)用offset的setter方法, 而在MASConstraint類中的setter方法依然是一個抽象方法, 本類中進行的是空實現(xiàn), 所以在MASViewConstraint 和MASCompositeConstraint 兩個子類中分別進行了實現(xiàn), 而兩個子類中的實現(xiàn)最終都會來到MASViewConstraint中setOffset方法如下:
- (void)setOffset:(CGFloat)offset {
self.layoutConstant = offset;
}
此方法實質(zhì)就是記錄了當前約束的偏移量, 以待后續(xù)使用
4. MASViewAttribute
MASViewAttribute是用來記錄view和要指定的約束的, 它的內(nèi)容較少, 比較簡單, 包含當前view屬性,和當前view的指定約束
例如第一個item的left約束, 通過構造方法可以生成MASViewAttribute 實例;
5. 約束的安裝
通過以上四個部分的分析, 我們已經(jīng)完成了block代碼塊中的所有分析, 接下來繼續(xù)來看 View+MASAdditions 中的 mas_makeConstraints方法, 在方法內(nèi)部執(zhí)行完block之后, 緊接著執(zhí)行 [constraintMaker install]; install方法如下
@implementation MAS_VIEW (MASAdditions)
- (NSArray *)install {
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) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
[self.constraints removeAllObjects];
return constraints;
}
@end
install方法, 首先判斷該約束是否已經(jīng)存在, 如果存在則需要先uninstall, self.removeExisting默認是NO, 因為在執(zhí)行mas_updateConstraints或者mas_remakeConstraints方法中將其設置為YES, 而這兩個方法最終都會調(diào)用insatll方法; View+MASAdditions 分類中install方法最后就是遍歷 maker持有的constraints數(shù)組, 分別進行安裝由于數(shù)組中的約束可能MASViewConstraint類型, 也可能是MASCompositeConstraint類型, 所以再這兩個類中分別有實現(xiàn)install方法, 不過最終調(diào)用還是來到MASViewConstraint中的install方法, 這里我們只對MASViewConstraint中的install方法進行分析, 方法實現(xiàn)如下
- (void)install {
if (self.hasBeenInstalled) {
return;
}
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;
}
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;
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
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
} else {
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}
核心內(nèi)容就是將MASViewConstraint中所持有的數(shù)據(jù), 進行解析 ,并調(diào)用系統(tǒng)的自動布局方法進行設置約束, 這里不做贅述, 但看下面這段
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;
}
我們調(diào)用系統(tǒng)的自動約束布局時, 需要清楚將約束安裝到哪個view上, 而上面這段正式找到要安裝的view, 按照系統(tǒng)自動約束的規(guī)則, 如果是size, 寬高約束需要作用的view本身, 如果是上下左右約束需要找到合適的view上, 所以通過以上判斷獲取到合適的installedView, 第一個分支中mas_closestCommonSuperview方法是求兩個視圖的最近父視圖, 這個方法可以著重看一下, 這里不再贅述!
總結(jié)
通過以上分析, 我想對各位讀者分析Masonry框架有很大幫助, 還有不少細節(jié)需要讀者自行分析! 最后,筆者在總結(jié)一下Masonry中的重點內(nèi)容:
-
鏈式編程/函數(shù)式編程
這個在Masonry中多數(shù)方法都是采用的這個編程方式, 雖然框架內(nèi)部實現(xiàn)相對復雜, 但是對于調(diào)用這來說極其簡潔明了, 這個是一個優(yōu)秀框架最難得的地方; 由于OC的方法調(diào)用是用方括號實現(xiàn), 所以在實現(xiàn)鏈式編程時相對比較麻煩一點, 但是作者巧妙使用block, 以及OC中Getter和Setter的語法糖(點語法), 在外形上實現(xiàn)了鏈式編程的效果, 這一點值得學習和深思!
-
抽象類實現(xiàn)
由于Xcode并沒有抽象類的校驗, 所以抽象類類中抽象方法極其容易忽略或者忘記, Masonry作者采用了OC的多多態(tài)特性, 在父類中進行了拋出錯誤的空實現(xiàn)一次來達到父類方法子類必須實現(xiàn)的效果!
-
Setter和Getter方法靈活應用
在本文第3部分末尾有描述, offset 的 get方法獲得獲取的Block類型, 而Setter方法傳入的是CGFloat, 這里實際上只是巧用OC語法糖實現(xiàn)了self.offset = offset 看起來好像類型都不匹配的代碼!
更多優(yōu)質(zhì)文章和內(nèi)容請關注筆者公眾號(碼農(nóng)的奮斗日記: lifeRecording)