Masonry源碼分析---名字就是這麼沒特色

Masonry 結構圖,本文會按照程序的執行來說解。 本文主要分兩部分,第一部分是約束的安裝,主要看約束的整個操做過程;第二部分分析Block代碼塊中的操做 (下圖是由做者青玉伏案 所作,做者作的很漂亮這裏拿來參考一下)。html

Masonry結構圖

第一部分 約束的安裝部分

1. View+MASAdditions categary方法入口

1.1 方法入口通常以下,咱們從頭開始分析:

//添加約束
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
//更新約束
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
//從新添加約束
- (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
複製代碼

1.2 首先查看添加約束的方法,方法以下:

其實這段代碼很簡單,就是建立一個約束製造者,將block中的約束添加後更新約束,更新約束和從新添加約束代碼相似,比第一種方式添加了updateExisting和removeExisting的設置數組

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    //translatesAutoresizingMaskIntoConstraints能夠自動將frame轉化爲約束,設置約束時須要關閉
    self.translatesAutoresizingMaskIntoConstraints = NO;
    //約束製造者 --- 初始化,而且初始化約束的數組
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    //設置block的約束 --- 回到block的操做
    block(constraintMaker);
    //升級
    return [constraintMaker install];
}
複製代碼

1.3 這一段代碼暫時跳過,下文講到MASViewConstraint類的install方法時回來查看就能夠更快的明白做者的意圖

- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
    //獲取兩個視圖最近的公共父視圖 -----> 就是說一層一層的父視圖找,直到兩個視圖的父視圖是一個的時候 跳出循環
    //目的是爲了給兩個view添加約束時找到他的父視圖
    MAS_VIEW *closestCommonSuperview = nil;

    MAS_VIEW *secondViewSuperview = view;
    while (!closestCommonSuperview && secondViewSuperview) {
        MAS_VIEW *firstViewSuperview = self;
        while (!closestCommonSuperview && firstViewSuperview) {
            if (secondViewSuperview == firstViewSuperview) {
                closestCommonSuperview = secondViewSuperview;
            }
            firstViewSuperview = firstViewSuperview.superview;
        }
        secondViewSuperview = secondViewSuperview.superview;
    }
    return closestCommonSuperview;
}
複製代碼

2. MASConstraintMaker 約束管理者

上面在分類中調用了MASConstraintMaker中的兩個方法,初始化操做和install操做,代碼以下less

2.1 約束管理者初始化,主要是初始化一個存儲約束的數組

- (id)initWithView:(MAS_VIEW *)view {
    self = [super init];
    if (!self) return nil;
    //當前視圖
    self.view = view;
    //存儲約束的數組
    self.constraints = NSMutableArray.new;
    return self;
}
複製代碼

2.2 install操做

- (NSArray *)install {
    //這裏判斷是不是從新添加約束
    if (self.removeExisting) {
        //獲取view中存在的約束
        NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
        //獲取到的數組挨個調用卸載方法,刪除約束
        for (MASConstraint *constraint in installedConstraints) {
            [constraint uninstall];
        }
    }
    //self.constraints將block中添加的約束進行遍歷
    NSArray *constraints = self.constraints.copy;
    for (MASConstraint *constraint in constraints) {
        //設置約束的updateExisting,若是調用的是更新方法在此處進行更新
        constraint.updateExisting = self.updateExisting;
        //將約束安裝
        [constraint install];
    }
    //安裝完成移除全部的約束
    [self.constraints removeAllObjects];
    return constraints;
}
複製代碼

3. MASViewConstraint 具體的約束

3.1 原生類的操做

爲了理清這個類的做用,咱們先看一下蘋果爲咱們提供的原生操做(以下),能夠猜想這個類主要的操做就是下面重點標記的那一步操做ui

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:@[

    //重點看這一步的操做
    [NSLayoutConstraint constraintWithItem:view1
                                 attribute:NSLayoutAttributeTop
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:superview
                                 attribute:NSLayoutAttributeTop
                                multiplier:1.0
                                  constant:padding.top]
 ]];
複製代碼

3.2 installedConstraintsForView方法

這裏給view動態添加了一個mas_installedConstraints的get方法,裏面存儲了install中添加的方法,下面的方法會往這個數組中添加MASViewConstraint屬性spa

+ (NSArray *)installedConstraintsForView:(MAS_VIEW *)view {
    return [view.mas_installedConstraints allObjects];
}
複製代碼

3.3 install中的操做

3.3.1 咱們先看下install方法中的操做

方法比較長,先看其中最重要的方法,感受和蘋果原生的使用很像,進入MASLayoutConstraint的頭文件發現 @interface MASLayoutConstraint : NSLayoutConstraint,其實就是在這將約束添加到視圖上的3d

//生成一個NSLayoutConstraint
 MASLayoutConstraint *layoutConstraint
 = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                        attribute:firstLayoutAttribute
                                        relatedBy:self.layoutRelation
                                           toItem:secondLayoutItem
                                        attribute:secondLayoutAttribute
                                       multiplier:self.layoutMultiplier
                                         constant:self.layoutConstant];
複製代碼
3.3.2 咱們來詳細分析這段代碼每一句的意思

暫時忽略,代碼中講到mas_closestCommonSuperview方法能夠在這進行跳轉代理

- (void)install {
    //判斷約束是否存在 不存在就直接返回
    if (self.hasBeenInstalled) {
        return;
    }
    //[self supportsActiveProperty]判斷是否能響應
    if ([self supportsActiveProperty] && self.layoutConstraint) {
        //讓約束起做用
        self.layoutConstraint.active = YES;
        //[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
        return;
    }
    //獲取父視圖和當前視圖及對應的約束 這裏的Attribute是視圖和約束的一個封裝類
    MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
    NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
    MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item; 
    NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;

    //若是secondViewAttribute不存在而且firstViewAttribute約束不是寬高,那麼就把view的父視圖及約束設置給secondViewAttribute
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }
    //生成一個NSLayoutConstraint
    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;
    //獲得兩個視圖當前的公共superview
    if (self.secondViewAttribute.view) {
        /******************************************* / mas_closestCommonSuperview 這個方法具體操做能夠回到View+MASAdditions類中查看就是上文告訴你們暫時跳過的地方 */
        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;
    }

    //若是是更新的話,判斷具體更新的參數是什麼 可到layoutConstraintSimilarTo中查看
    MASLayoutConstraint *existingConstraint = nil;
    if (self.updateExisting) {
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    }
    
    if (existingConstraint) {
        // 若是隻是更新的話,更新一下這個約束
        existingConstraint.constant = layoutConstraint.constant;
        self.layoutConstraint = existingConstraint;
    } else {
        //添加layout到父視圖,而且把約束類添加到mas_installedConstraints中
        [self.installedView addConstraint:layoutConstraint];
        self.layoutConstraint = layoutConstraint;
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}
複製代碼

3.4 uninstall中的操做

看完了上面的代碼就能夠鬆一口氣了,下面的uninstall過程就很簡單了code

- (void)uninstall {
    //判斷可否響應active
    if ([self supportsActiveProperty]) {
        //讓約束不起做用
        self.layoutConstraint.active = NO;
        //從數組裏刪除掉這個約束
[self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
        return;
    }
    //從父視圖刪除約束
    [self.installedView removeConstraint:self.layoutConstraint];
    self.layoutConstraint = nil;
    self.installedView = nil;
    //刪除約束
    [self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
}
複製代碼

第二部分 Block代碼塊操做

這部分操做講述以前先上一張圖,不知道你們有沒有和我相同的疑問,那就是left和right調用顏色爲何不一樣,看完這部分你們就會有本身的答案了cdn

代碼截圖

感興趣的朋友能夠把下面代碼粘貼到IDE試一下htm

[self.bottomView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.and.bottom.equalTo(self);
            make.top.equalTo(self.topView.mas_bottom);
            make.height.equalTo(self.topView);
        }];
複製代碼

1. make.top.equalTo(self.topView.mas_bottom)

咱們先來分析標題中的代碼調用的方式

1.1 maker的屬性調用流程及其結果

1.1.1 調用的left/right/top/bottom等方法

left/right/top/bottom.....,調用的方法相同

- (MASConstraint *)top {
    //添加不一樣的約束屬性 返回值是約束的屬性
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}
複製代碼
1.1.2 添加屬性,並返回一個MASViewAttribute對象

添加屬性到constraints數組中,第一次進入constraint爲空,那麼去掉第一個判斷的內容,代碼以下

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    //初始化一個MASViewAttribute,存放view和layoutAttribute
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    //初始化一個MASViewConstraint
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    if (!constraint) {
        //設置代理方法
        newConstraint.delegate = self;
        //有的話就添加到constraints
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}
複製代碼

1.2 equalTo調用分析

1.2.1 查找equalTo具體調用

經過對equalTo的查找發現調用的是以下的代碼,能夠看到這部分方法中調用的代碼是相同的,因爲MASAttribute是一個抽象類,在類中沒有找到其實現方法,經過上一節返回類型MASViewAttribute查找其具體實現

- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

- (MASConstraint * (^)(id))mas_equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

- (MASConstraint * (^)(id))greaterThanOrEqualTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationGreaterThanOrEqual);
    };
}

- (MASConstraint * (^)(id))mas_greaterThanOrEqualTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationGreaterThanOrEqual);
    };
}

- (MASConstraint * (^)(id))lessThanOrEqualTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationLessThanOrEqual);
    };
}

- (MASConstraint * (^)(id))mas_lessThanOrEqualTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationLessThanOrEqual);
    };
}
複製代碼
1.2.2 equalToWithRelation調用
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if ([attribute isKindOfClass:NSArray.class]) {
            NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
            NSMutableArray *children = NSMutableArray.new;
            for (id attr in attribute) {
                MASViewConstraint *viewConstraint = [self copy];
                viewConstraint.layoutRelation = relation;
                viewConstraint.secondViewAttribute = attr;
                //約束數組
                [children addObject:viewConstraint];
            }
            //這是一個組合約束
            MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
            compositeConstraint.delegate = self.delegate;
            //把maker的constraints數組中的單個約束MASViewConstraint替換成MASCompositeConstraint這種組合約束
            [self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
            return compositeConstraint;
        } else {
            NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
            //若是是NSValue直接給MASViewConstraint賦值
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}
複製代碼

2. make.left.right.and.bottom.equalTo(self)


咱們先來分析標題中的代碼調用的方式,區別於第一種,這種屬於組合形式

2.1 類MASCompositeConstraint介紹

MASCompositeConstraint,一個組合約束類,集成自抽象類MASConstraint,其屬性childConstraints數組包含了其組合的MASViewConstraint約束類,也就是說當一個block代碼塊中一行添加的約束超過一個之後,masonry內部會將約束統一更新爲MASCompositeConstraint類,並在最後將MASCompositeConstraint實例添加到maker的constraints中

2.2 當約束超過一個時的調用

約束爲一個的時候與上一小節代碼執行順序沒有區別,也就是說執行make.left的時候與上文的調用方式相同,可是當調用到right的時候,程序的調用方式發生改變,改變以下(你們能夠對照源碼查看)

//調用.right首先調用下面代碼
#import "MASConstraint.h"
- (MASConstraint *)right {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];
}
//接着會走子類方法
#import "MASViewConstraint.h"
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
    //能夠看到此時的constraint已經賦值了,delegate指向maker類
    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
//調用maker類中的代理方法
#import "MASConstraintMaker.h"
- (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;
        //把maker的constraints數組中的單個約束MASViewConstraint替換成MASCompositeConstraint這種組合約束
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    //這部分就不會執行了
    if (!constraint) {
        newConstraint.delegate = self;
        //有的話就添加到constraints
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}
複製代碼

至此masonry的分析就完成了,剩下的就是一次次的重複調用了

結語

masonry的鏈式語法很漂亮,有時間會給你們分析一下

相關文章
相關標籤/搜索