RAC之masonry源碼深度解析

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中有一個庫MasonryNSLayoutConstraint進行了封裝,*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方法

實際的過程是:

  1. 判斷是否有約束,有就遍歷約束,調用uninstall清空之前所有的約束
  2. 無約束,就遍歷數組的約束對象,然後調用install逐個安裝
  3. 調用系統的方法安裝約束
- (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作爲返回值比較難理解,但這是整個鏈式編程的核心。有什麼疑問也可以在評論出留言。
如果你喜歡我的原創文章,也渴望你的贊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章