Masonry實現原理並無那麼可怕

想必在AFNetworking以後,Masonry成了廣大iOS開發者平常開發不可或缺的三方庫之一。它的使用真的很是簡單,例如:數組

[greenView makeConstraints:^(MASConstraintMaker *make) {
        make.top.greaterThanOrEqualTo(superview.top).offset(padding);
        make.left.equalTo(superview.left).offset(padding);
        make.bottom.equalTo(blueView.top).offset(-padding);
        make.right.equalTo(redView.left).offset(-padding);
        make.width.equalTo(redView.width);

        make.height.equalTo(redView.height);
        make.height.equalTo(blueView.height);
        
    }];
複製代碼

從這段短小精悍的代碼中,咱們已經可以挖掘出它背後的原理。本文假設你對Masonry已經有基本的瞭解和使用。

首先咱們提取一些「關鍵詞」,他們到底是啥意思?bash

1.makeConstraints:佈局

2.makeui

3.left、right、height...spa

4.equalTo()3d

1.makeConstraints:

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}
複製代碼
  1. MASConstraintMaker被傳入了block()中,即MASConstraintMaker實例就是咱們的第二個「關鍵詞」---make。從[constraintMaker install]還可看出,make負責了約束的添加。後文詳細介紹make代理

  2. translatesAutoresizingMaskIntoConstraints,官方解釋大體爲:默認狀況下它是YES,即viewautoresizing mask會自動成爲它的佈局。若是咱們但願手動佈局,須要將它設爲NOcode

2.make(MASConstraintMaker)

從鏈式調用make.top.equalTo...,咱們能夠看出,make中定義了這些約束屬性,它們是這樣實現的:cdn

// step 1
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

// step 2
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

// step 3
- (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;
}
複製代碼
  1. MASViewAttribute 從實現文件中得知,MASViewAttribute = View + NSLayoutAttribute + item,這是一個可變元組,存儲了View和與它相關的約束信息。對象

  2. MASViewConstraint 這就是一個約束,它包含firstViewAttributesecondViewAttribute

  3. 由step2可知,單純的約束屬性在該方法下的第一個參數都是nil,因此咱們先直接看這種狀況下的step3的執行狀況。它被加入了一個約束數組中。

if (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
複製代碼

4)鏈式調用的實現 咱們知道Masonry鏈式調用是它廣受好評的優勢之一。那麼,是如何作到make.top.left這樣的操做呢? 由step3得知,make.top的返回類型是MASViewConstraint,那MASViewConstraint中是如何調用到left的呢? 從MASViewConstraint的父類MASConstraint能夠看到,這裏也定義了全部的佈局屬性,而這些佈局屬性的實現方式,以下,例:

// MASConstraint.m
- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}

// MASViewConstraint.m
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");

    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
複製代碼

MASViewConstraint把這個方法的具體實現委託給了代理方法。而在step3中,這個代理正是MASConstraintMaker。不一樣的是,此時,step2方法的參數不在是nil了,而是當前約束屬性。這也是的step3的處理邏輯不同了。(回到step3中再看看!)此時,MASCompositeConstraint登場了,它是一個約束組合。以make.top.left...爲例,這個約束組合包含了topleft,而且調用了shouldBeReplacedWithConstraint:方法,以下:

- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
    NSUInteger index = [self.constraints indexOfObject:constraint];
    NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
    [self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
複製代碼

make.top.left...爲例,這個方法找到了以前存儲top約束的位置,並替換成了約束組合。最終,topleft就一塊兒被加入和make的約束數組中。

3.install

從上文看,咱們已經拿到了全部的約束。這些約束是如何加入到視圖上的?來看看install方法。 摘錄一下MASConstraintMaker中的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;
}
複製代碼

make的處理相對簡單,再看看MASViewConstraint自己是如何install的。因爲方法太長,我不粘貼完整的代碼了(分開來會比較容易看懂)。大體的流程以下: 1.如何約束是設置widthheight,這是視圖自身的屬性,則把當前視圖的父視圖做爲關聯視圖(代碼中爲secondLayoutItem)。即這兩個約束是相對於父視圖設置的。

2.若是佈局上存在相對視圖,即一般寫法中的equalTo(someView.mas_top)這樣,則找到這2個視圖最近的公共父視圖,並把約束添加在這個父視圖上。代碼以下:

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;
    }
複製代碼

3.最後,若是是更新約束操做,則找出須要更新的約束單獨修改;不然,爲視圖添加約束,並記錄在mas_installedConstraints中。

到這裏,約束的添加已經徹底梳理明白了。

4.equalTo()

仍是得看具體實現方法:

- (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;
            [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;
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}
複製代碼

這裏就比較好理解了,若是傳入的是約束數組,則把他包裝成MASCompositeConstraint,以一樣的方式加入到makeconstraints中。若是是單個約束,則將其設爲secondViewAttribute,用於install的時候使用。

總結

1.MASConstraintMaker做爲工廠,生產一個個MASViewConstraint約束對象。 2.MASViewConstraintMASCompositeConstraint繼承於抽象類MASConstraint,爲咱們提供了高度封裝的約束對象 3.View+MASAdditions這個UIView的擴展是Masonry與外界交互的接口類,這樣很好的把複雜的約束邏輯封裝在內部管理,又提供了簡單的API供用戶使用。

你看懂了嘛~

相關文章
相關標籤/搜索