Masonry 源碼解讀

先簡(jiǎn)單看一下 Masonry 主要的設(shè)計(jì)以及 Class 結(jié)構(gòu)方法

Masonry 采用了經(jīng)典的 Composite 設(shè)計(jì)模式,如果你還不清楚該設(shè)計(jì)模式,你需要 Google 看看對(duì)應(yīng)的文章

看完了上面類設(shè)計(jì)圖,我們開(kāi)始跟蹤程序

Masonry 開(kāi)始于這樣的代碼結(jié)構(gòu)

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIView *aview = [[UIView alloc] init];
    [self.view addSubview:aview];
    
    [aview mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.view);
        make.left.right.equalTo(@0);
    }];
}

其中, mas_makeConstraints 方法在 View+MASAdditions 被定義

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}

代碼的第二行創(chuàng)建了一個(gè) MASConstraintMaker(MASConstraintMakerMASConstraint 一樣 定義了 left right 等方法) 對(duì)象,也就是我們能夠進(jìn)行鏈?zhǔn)讲僮?br> make.left.right.equalTo 開(kāi)始對(duì)象

第三行執(zhí)行代碼 block(constraintMaker), 也就是執(zhí)行我們寫的 make.left.right.equalTo 的代碼

下面開(kāi)始解析這一段鏈?zhǔn)酱a

MASConstraintMaker 內(nèi)部有一個(gè) NSMutableArray<MASConstraint *> *constraints 對(duì)象, constraints 保存了每一條 make 出來(lái)的信息,比如如果你寫這樣的代碼

make.top.equalTo(self.view);
make.left.right.equalTo(@0);

constraints 將會(huì)保存 2 個(gè)對(duì)象,第一個(gè)對(duì)象記錄了 make.top.equalTo(self.view) 的所有信息,第二個(gè)對(duì)象記錄了 make.left.right.equalTo(@0) 的所有信息,而對(duì)象的數(shù)據(jù)結(jié)構(gòu)就是上圖中的 MASViewConstraintMASCompositeConstraint

接下來(lái),我們看一看它是如何保存這個(gè)信息的:

先看第一個(gè)層級(jí):

make.top

調(diào)用順序如下:

- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (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;
}

其中上面注釋的代碼是本次調(diào)用不會(huì)運(yùn)行的,后面會(huì)講到注釋掉的代碼的作用

介紹一下 MASViewAttribute

MASViewAttribute 的 Class 結(jié)構(gòu)如下圖

上面的 item 字段,Masonry 上給的是 id 字段,因?yàn)?還有 viewControllertopLayoutGuide 屬性,這里 為了方便理解,可以把它直接看作 UIView

剩余的代碼很簡(jiǎn)單,只是根據(jù) top 這個(gè)屬性,新建你一個(gè) MASViewConstraint 的對(duì)象,然后為 MASViewConstraint1 創(chuàng)建firstViewAttribute(這里還沒(méi)有生成secondViewAttribute`)

相當(dāng)于下面的代碼:

//self  是外部的 [aView mas_makeConstraints] 的 aView
MASViewConstraint *newConstraint.firstViewAttribute.item = self.view //外部調(diào)用 make.top 的 aview
newConstraint.firstViewAttribute.layoutConstraint = NSLayoutAttributeTop

最后 把 這個(gè) newConstraint 加入到 MASConstraintMaker(make)constraints 對(duì)象中

這樣 完成了 make.top 的操作 (.equalTo 的操作稍后介紹)

接著,我們看第二句

make.left.right

其中,前面半句 make.left 和上面的步驟是一樣的,不同的地方在于后面 .right

首先,前半句 make.left 返回了 MASAttribute(MASViewConstraint)對(duì)象,已經(jīng)不是 MASConstraintMaker(make) 對(duì)象了。所以 我們需要看看 MASViewConstraintleft 方法做了那些事情。

調(diào)用順序如下:

- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");

    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];    //這里 delegate 指向了剛剛的 `MASConstraintMaker(make)` 對(duì)象
}
- (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;
}

這一次最后這個(gè)函數(shù)的執(zhí)行了上階段注釋的代碼,這段代碼和之前的代碼大致相同,也是先創(chuàng)建了一個(gè) MASViewConstraint 的對(duì)象, 但是,不同之處在于這次組成了一個(gè) MASCompositeConstraintMASCompositeConstraintchildConstraints 放了上次的 make.left 的信息和這次 make.right 的信息,并且 用 MASCompositeConstraint 替換了原來(lái)的 MASViewConstraint。 (這里希望你理解 Composite 設(shè)計(jì)模式的精髓, 因?yàn)?類似這種make.left.right.top三連以上的鏈?zhǔn)骄涫?MASCompositeConstraint 里面放的是兩個(gè)MASCompositeConstraint)

最后是 equalTo 的語(yǔ)法

先看一下 equalTo 的定義

- (MASConstraint * (^)(id attr))equalTo

equal 是一個(gè) 返回 MASConstraint * 里面包括一個(gè)參數(shù) attr 名為 equalTo 的 block

之所以是 attr 被定義為 id 是因?yàn)?我們可以寫出這樣的代碼

make.left.equalTo(self.view) 或者 make.left.equalTo(@0) 

整個(gè) equalTo 的調(diào)用順序如下(selfMASConstraint對(duì)象)

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

- (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;
//        }
    };
}

這里 我只列舉出了 MASViewConstraintequalToWithRelation, 因?yàn)?Composite 模式中定義的 equalToWithRelation 其實(shí)就是讓所有的子類依次去執(zhí)行 equalToWithRelation,另外 我并沒(méi)有怎么使用過(guò) equalTo(attr) attr 是一個(gè) NSArray 類型,所以 注釋的代碼沒(méi)有去鉆研。

MASViewConstraintsecondViewAttribute 被定義如下

- (void)setSecondViewAttribute:(id)secondViewAttribute {
    if ([secondViewAttribute isKindOfClass:NSValue.class]) {
        [self setLayoutConstantWithValue:secondViewAttribute];
    } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
        _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);
    }
}

這也是為什么我們能夠?qū)戇@樣的代碼

make.left.equalTo(@0) // make.left.equalTo(self.view)

equalTo 后面可以接 NSNumberUIView 甚至是 NSValue

好了,到此為止,所有的鏈?zhǔn)酱a已經(jīng)解讀完畢,至于 類似的 offset 道理都一樣,相對(duì)簡(jiǎn)單,這里不再做過(guò)多的陳述

最后是 [constraintMaker install] 的代碼

調(diào)用函數(shù)如下(selfMASConstraintMaker):

- (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;
}

上面的代碼 removeExistingmas_remakeConstraints 做的事情, Masonry 用一個(gè) MutableSet 記錄了所有通過(guò) Masonry 創(chuàng)建的 constraint, 這個(gè) set 被定義在了 MASConstraints.m 里面

#define MAS_VIEW UIView

@interface MAS_VIEW (MASConstraints)

@property (nonatomic, readonly) NSMutableSet *mas_installedConstraints;

@end

@implementation MAS_VIEW (MASConstraints)

static char kInstalledConstraintsKey;

- (NSMutableSet *)mas_installedConstraints {
    NSMutableSet *constraints = objc_getAssociatedObject(self, &kInstalledConstraintsKey);
    if (!constraints) {
        constraints = [NSMutableSet set];
        objc_setAssociatedObject(self, &kInstalledConstraintsKey, constraints, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return constraints;
}

@end

每次通過(guò) Masonry 添加最后添加成功的 constraint 都會(huì)被加入到這個(gè) set 里面

函數(shù)的最后把所有添加到 MASConstraintMakerconstraints 的數(shù)組清空(因?yàn)樗械?constraint 都已經(jīng)被加入到了 View 里)

最后 我們看看 install 的代碼

NSArray *constraints = self.constraints.copy;   //這里為什么用 copy 我也不是很清楚作者是怎么想的,可能是出于線程安全的考慮
for (MASConstraint *constraint in constraints) {
    constraint.updateExisting = self.updateExisting;
    [constraint install];
}

[constraint install] 也是利用了 Composite 設(shè)計(jì)模式的特性(希望你能真正理解 Composite

MASViewConstraint 的 install 實(shí)現(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;
    }
    ////---------截止這里的代碼都很好理解 就是在配好 NSLayoutConstraint 的所有 item 和 attribute, 而這些東西在之前的 make 鏈?zhǔn)秸Z(yǔ)法都組裝好了---------////
    
    ////---------其中 masonry 自己的注釋也寫明白了 為什么 make.left.equalTo(@10) 這樣的代碼能夠被組裝成 NSLayoutConstraint---------////     
    //MASLayoutConstraint 可以簡(jiǎn)單的認(rèn)為就是 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;
    
    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];
    }
}

這段代碼是 install 的最后一個(gè)步驟,整體來(lái)說(shuō)比較簡(jiǎn)單,就是把 MASViewConstraint 變成 NSLayoutConstraint 加入到具體的 UIView 當(dāng)中,如果你還不清楚怎么用系統(tǒng)的 API 寫 AutoLayout 的話,可以點(diǎn)這里。

其中 mas_closestCommonSuperview 是找 firstViewAttribute.viewsecondViewAttribute.view 的共同公共的父 View, 這里的算法很簡(jiǎn)單,只是單純的遍歷

- (instancetype)mas_closestCommonSuperview:(MAS_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;
}

截止為止,Masonrymas_makeConstraints 的整個(gè)過(guò)程全部分析完畢,對(duì)于剩下的 mas_remakeConstraintsmas_updateConstraints 大同小異,這里不再做更多陳述

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容