RAC之masonry源碼深度解析
寫在前面:
本文不是講解masonry的基礎使用,而是希望藉着masonry的源碼解析給大家滲透鏈式編程的思想和展示其具體實現。
現在RAC(ReactiveCocoa)很火,藉着這個成熟的案例讓大家窺其一斑,作者在此拋磚引用,供大家交流參考。
一、NSLayoutConstraint約束
實際iOS用NSLayoutConstraint
對控件進行約束。比如:想要讓子控件的頂部距離父控件頂部10pt,添加約束的實際條件就是滿足subView.top = superView.top * 1 + 10
這個公式就可以了。
NSLayoutConstraint
的實際就是對該公式的代碼解釋,代碼如下:
NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:subView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top];
[self.view addConstraints:topConstraint];
但我們需要對控件的top,bottom,left,right進行約束就特別麻煩。在OC中有一個庫Masonry
對NSLayoutConstraint
進行了封裝,(*Swift*中使用*SnapKit*,*SnapKit*其實就是*Masonry*的*Swift*版本,實現思路大體一致。)
二:masonry介紹
masonry是iOS佈局控件的輕量級框架。其原理是通過鏈式調用的方式對NSLayoutConstraint
進行封裝,簡化了控件的約束方式。
抓住兩頭:
其實massory最終還是利用蘋果官方提供的NSLayoutConstraint
,只是利用鏈式編程的方式進一步封裝。
接下來思考兩個問題
1. 怎麼通過封裝?
2. 鏈式編程來實現約束的添加的?
接下來我們就對masonry的封裝做進一步解釋。
1.masonry添加約束的代碼實現
- (void)viewDidLoad {
[super viewDidLoad];
UIView *subView = [[UIView alloc]init];
subView.backgroundColor = [UIColor purpleColor];
//先添加控制,後設置約束,不然找不到約束的依賴,會報錯。
[self.view addSubview:subView];
[subView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.equalTo(@20);
make.right.bottom.equalTo(@-10);
}];
}
2.masonry方法執行步驟解析:
- 子控件調用
mas_makeConstraints
方法,mas_makeConstraints
方法有個block參數(返回值爲void,參數爲MASContraintMaker
的實例對象make);- block作爲方法的參數就是隱式調用(block並沒有真正調用,需要在方法內部,block()調用一次,纔會真正執行block);
- block的有一個MASContraintMaker類的實例make作爲參數,讓make去添加約束;
- MASContraintMaker類中有個可變數組的屬性,用於保存約束;
- 執行
mas_makeConstraints
傳入進行的block;- 遍歷數組中的約束,完成約束的安裝;
以上只是文字描述了執行的大致步驟,具體的代碼實現是怎麼樣的呢?
我們接下里通過三個問題來展開。
3.疑問
> 1. make的點語法代表什麼意思?
> 2. 爲什麼可以連續用點語法?
> 3. 具體代碼解析是什麼樣的?
問題一:make的點語法代表什麼意思?
make.left.top.equalTo(@20);
實質就是MASContraintMaker
類的實例對象make調用了屬性的getterter方法。
扒開源碼我們會看到
@interface MASConstraintMaker : NSObject
@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
//省略了bottom,right,baseline等屬性。
@end
//getter方法,返回的是MASConstraint對象,getter方法調用 addConstraintWithLayoutAttribute:
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
//返回的是MASConstraint對象,接着調用constraint: addConstraintWithLayoutAttribute:方法
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
- (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;
}
問題二:爲什麼可以連續用點語法?
鏈式編程的核心:
每個點語法實際調用的getter方法,getter方法的返回值爲實例對象本身,然後繼續調用getter方法,就成爲鏈式了。
結合代碼進行具體解釋:make.left.top.equalTo(@10);
我們對其分開解釋:點語法返回的時一個新的約束newConstraint。
`make.left.top.equalTo(@10);`
//分開寫就爲
newConstraint1 = make.left;
newConstraint2 = newConstraint1.top;
newConstraint2.equalTo(@10);
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
問題三:整個方法的具體調用步驟是什麼樣的?
首先解釋下MASConstraintMaker
類:
MASConstraintMaker
類就是一個工廠類,負責創建MASConstraint
類型的對象(依賴於MASConstraint
接口,而不依賴於具體實現)
粗略步驟:
1. UIView的類調用mas_makeConstraints
方法
2.mas_makeConstraints
有個block參數,會做隱式回調
3. 獲得約束數組,通過install安裝約束。
1.mas_makeConstraints
方法解析
用戶是UIView調用擴展的UIView+MASAdditions
分類的mas_makeConstraints
方法來爲當前視圖添加約束的。
mas_makeConstraints方法的返回值是一個數組(NSArray),數組中所存放的就是當前視圖中所添加的所有約束。因爲Masonry框架對NSLayoutConstraint封裝成了MASViewConstraint,所有此處數組中存儲的是MASViewConstraint對象。
接下來來看mas_makeConstraints
的參數,mas_makeConstraints
測參數是一個類型爲void(^)(MASConstraintMaker *)
的匿名block(也就是匿名閉包),該閉包的返回值爲void, 並且需要一個MASConstraintMaker
工廠類的一個對象。該閉包的作用就是可以讓mas_makeConstraints
方法通過該block給MASConstraintMaker
工廠類對象中的MAConstraint
屬性進行初始化。
具體可以參考下面的代碼及其註釋:
//新建並添加約束
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
//關閉自動添加約束,由我們手動添加約束
self.translatesAutoresizingMaskIntoConstraints = NO;
//實例化constraintMaker對象,來操作接下來的約束
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
//block作爲參數,這裏完成隱式調用,完成回調,通過block將constraintMaker對象回調給用戶讓用戶對constraintMaker中的MAConstraint類型的屬性進行初始化。換句話說block中所做的事情就是之前用戶設置約束是所添加的代碼,比如make.top(@10) == ( constraintMaker.top = 10 )。
block(constraintMaker);
//添加約束,但會Install的約束數組
return [constraintMaker install];
}
2. block參數的隱式回調
返回的值爲一個block,block的返回值是MASConstraint類的實例對象,所以最終還是返回的MASConstraint類的實例對象。
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
3.約束安裝install方法
實際的過程是:
- 判斷是否有約束,有就遍歷約束,調用uninstall清空之前所有的約束
- 無約束,就遍歷數組的約束對象,然後調用install逐個安裝
- 調用系統的方法安裝約束
- (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;
}
文末:
以上是masonry。通過這個也是給大家滲透鏈式編程的思想。
可能很多人對block作爲返回值比較難理解,但這是整個鏈式編程的核心。有什麼疑問也可以在評論出留言。
如果你喜歡我的原創文章,也渴望你的贊。