Masonry 原始碼解讀(上)

小橘爺發表於2018-04-28

前言

iOS 開發中的佈局方式,總體而言經過了三個時代。混沌初開之時,世間只有3.5英寸(iPhone 4、iPhone 4S),那個時候螢幕適配對於大多數 iOS 開發者來說並不是什麼難題,用 frame 就能精確高效的定位。這之後,蘋果釋出了4英寸機型(iPhone 5、iPhone 5C、iPhone 5S),與此同時蘋果也推出了 AutoresizingMask,用來協調子檢視與父檢視之間的關係。再之後,各種各樣的 iPhone 和 iPad 紛紛面世,不僅僅是螢幕尺寸方面的差異,更有異形屏(iPhone X)。在此期間,蘋果提出了 AutoLayout 技術,供開發者進行螢幕適配。

使用 AutoLayout 的方法也有兩種——通過 Interface Builder 或者純程式碼。前者一直是蘋果官方文件裡所鼓勵的,原因是蘋果從最初到現在,對於 iOS 應用的想法都是小而美的,在他們的認知裡,一個 APP 應該提供儘可能小的功能集,這也是為為何蘋果迄今為止官方推薦的架構仍然是 MVC,官方推薦的開發方式仍是以 StoryBoard(Size Classes)。但是在一些專案較大的公司,StoryBoard 的某些特性(導致應用包過大,減緩啟動速度,合併程式碼困難)又是不能為人所容忍的,便有了純程式碼來實現 View 層的一群開發者(比如我)。

如果你曾經用程式碼來實現 AutoLayout,你會發現蘋果提供的 API 的繁瑣程度令人髮指,這也是 Masonry 這類框架被發明的原因。Masonry 是一個輕量級的佈局框架,它使用更好的語法來封裝 AutoLayoutMasonry 有自己的佈局 DSL,它提供了一種鏈式的方式來描述你的 NSLayoutConstraints,從而得到更簡潔和可讀的佈局程式碼。

接下來,我們就從 MasonryREADME 提供的程式碼著手,看一看 Masonry 是如何幫助我們簡化繁瑣的 AutoLayout 程式碼的。

mas_makeConstraints:

舉一個簡單的例子,你想要一個檢視填充它的父檢視,但是在每一邊間隔10個點。

UIView *superview = self.view;

UIView *view1 = [[UIView alloc] init];
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
    make.left.equalTo(superview.mas_left).with.offset(padding.left);
    make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
    make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];
複製程式碼

我們想要實現約束的效果,是通過 mas_makeConstraints: 這個方法來實現的,這個方法可以在任意 UIView 類及其子類上呼叫,說明其是一個分類方法,這也是這個方法加了 mas_ 字首的原因。該方法宣告在 UIView+MASAdditions.h 檔案中,先來看一下這個方法的完整宣告:

- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
複製程式碼

這個方法傳遞的引數是一個引數為 MASConstraintMaker 型別的無返回值的 block,而該方法的返回值則是一個陣列。方法宣告中我們看到了一個叫做 NS_NOESCAPE 的巨集,NS_NOESCAPE 用於修飾方法中的 block 型別引數,作用是告訴編譯器,該 block 在方法返回之前就會執行完畢,而不是被儲存起來在之後的某個時候再執行。編譯器被告知後,就會相應的進行一些優化。更詳細的內容請參考 Add @noescape to public library API

接下來是方法的實現:

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

self.translatesAutoresizingMaskIntoConstraints = NO;

預設情況下,view 上的 autoresizing mask 會產生約束條件,以完全確定檢視的位置。這允許 AutoLayout 系統跟蹤其佈局被手動控制的 viewframe(例如通過 setFrame:)。當你選擇通過新增自己的約束來使用 AutoLayout 來定位檢視時,必須 self.translatesAutoresizingMaskIntoConstraints = NO;。IB 會自動為你做這件事。

constraintMaker

在這之後,創造了一個 MASConstraintMaker 型別的物件 constraintMakerMASConstraintMaker 的初始化方法為:

- (id)initWithView:(MAS_VIEW *)view;
複製程式碼

可以看到,傳入的 view 引數是由一個叫做 MAS_VIEW 的巨集來作為引數宣告的,MAS_VIEW 的定義是:

#if TARGET_OS_IPHONE || TARGET_OS_TV

    #import <UIKit/UIKit.h>
    #define MAS_VIEW UIView
    ...

#elif TARGET_OS_MAC

    #import <AppKit/AppKit.h>
    #define MAS_VIEW NSView
    ...

#endif
複製程式碼

因為 Masonry 是一個跨平臺的框架,於是通過預編譯巨集來讓在不同的平臺上,MAS_VIEW 代表的意義不同。接下來看初始化方法的實現:

- (id)initWithView:(MAS_VIEW *)view {
    self = [super init];
    if (!self) return nil;
    
    self.view = view;
    self.constraints = NSMutableArray.new;
    
    return self;
}
複製程式碼

在初始化方法中,將傳入的 view 通過一個弱指標(@property (nonatomic, weak) MAS_VIEW *view;)保留在了 constraintMaker 中,同時初始化了一個名為 constraintsNSMutableArray,用來儲存約束。

block(constraintMaker);

接著,constraintMaker 通過 block(constraintMaker); 傳遞給了我們,而我們對它做了什麼呢?

make.top.equalTo(superview.mas_top).with.offset(padding.top); 
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
複製程式碼

我們將對檢視約束的描述,以一種迥異於建立 NSLayoutConstraints 物件的方式描述了我們對檢視的約束,我們以其中的一句作為例子來看看 Masonry 是如何實現鏈式 DSL 的。

make.top.equalTo(superview.mas_top).with.offset(padding.top);
複製程式碼

make.top

makeMASConstraintMaker 型別的物件,這個型別封裝了一系列只讀 MASConstraint 屬性,top 就是其中之一,宣告和實現如下:

@property (nonatomic, strong, readonly) MASConstraint *top;
複製程式碼
- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
複製程式碼

addConstraintWithLayoutAttribute: 方法的實現為:

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

間接呼叫了 `constraint:addConstraintWithLayoutAttribute:` 方法:

- (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;
}
複製程式碼

讓我們一句一句來看:

MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
複製程式碼

首先,會初始化一個 MASViewAttribute 型別的物件(viewAttribute),該型別的初始化方法是:

- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute;
複製程式碼

實現為:

- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
    return self;
}

- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [super init];
    if (!self) return nil;
    
    _view = view;
    _item = item;
    _layoutAttribute = layoutAttribute;
    
    return self;
}
複製程式碼

MASViewAttribute 是一個模型類,用於儲存檢視和檢視對應的 NSLayoutAttribute

接下來會初始化一個 MASViewConstraint 型別的物件(newConstraint):

MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
複製程式碼

MASViewConstraint的初始化方法是:

- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute;
複製程式碼
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
    self = [super init];
    if (!self) return nil;
    
    _firstViewAttribute = firstViewAttribute;
    self.layoutPriority = MASLayoutPriorityRequired;
    self.layoutMultiplier = 1;
    
    return self;
}
複製程式碼

MASViewConstraint 也是一個模型類,會通過剛才初始化的 viewAttribute 作為初始化引數,並儲存在 _firstViewAttribute 的例項變數中。

接下來,由於 constraint 引數為 nil, 所以直接走到這裡:

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

newConstraint 物件的代理設為 self (make),同時將其放置到 constraints 陣列中。

簡而言之就是,make 中的 top 方法會初始化一個 MASViewAttribute 型別的物件 viewAttribute,並通過該物件初始化一個 MASViewConstraint 型別的物件 newConstraint,讓 make 作為 newConstraint 物件的 delegate,並儲存在 makeconstraints 屬性中。接下來,return newConstraint;

return newConstraint; 看似簡簡單單的一句程式碼,卻是 Masonry 這套鏈式 DSL 能夠生效的核心。

.equalTo

newConstraintMASViewConstraint 型別的物件,而 MASViewConstraint 又是 MASConstraint 的子類。在 MASConstraint 中,宣告瞭一系列的方法,例如:

/**
 *	Sets the constraint relation to NSLayoutRelationEqual
 *  returns a block which accepts one of the following:
 *    MASViewAttribute, UIView, NSValue, NSArray
 *  see readme for more details.
 */
- (MASConstraint * (^)(id attr))equalTo;
複製程式碼

這個方法的返回值是一個接受 id 型別的 attr 引數,返回 MASConstraint 型別的 block,這樣寫的意義是,既可以做到傳遞引數,返回 self,同時又確保了可以實現鏈式呼叫的 DSL

該方法的實現為:

- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}
複製程式碼

在方法的實現中,呼叫了一個名為 equalToWithRelation 的內部方法,方法的實現為:

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { MASMethodNotImplemented(); }
複製程式碼

MASConstraint 其實是一個基類,其中 equalToWithRelation 方法本身的實現裡只有一個名為 MASMethodNotImplemented(); 的巨集,這個巨集的實現僅僅是丟擲一個異常:

#define MASMethodNotImplemented() \
    @throw [NSException exceptionWithName:NSInternalInconsistencyException \
                                   reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
                                 userInfo:nil]
複製程式碼

而我們在 makeConstraints 的時候,實際呼叫的是 MASViewConstraint 這個 MASConstraint 子類中的實現:

- (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;
        }
    };
}
複製程式碼

該方法接收兩個引數,一個表示了對應的屬性(mas_top),一個表示了相等關係(NSLayoutRelationEqual),進入方法後會先對我們傳入的屬性做一個型別判斷,我們傳入的是一個單個的屬性,所以會落入 else 分支,同樣是依賴斷言做了一系列保護性的判斷,並將相等關係和檢視屬性分別賦值給 layoutRelationsecondViewAttribute 屬性,並返回 self

返回 self,看似簡簡單單的一個操作,卻是 Masonry 能夠實現鏈式 DSL 最重要的基石。(重要的事情說 n 遍)

superview.mas_top

再來看看我們傳入的 mas_top,這是一個宣告在 View+MASAdditions.h 當中的只讀屬性:

@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
複製程式碼

簡單生成並返回了一個 MASViewAttribute 屬性的物件:

- (MASViewAttribute *)mas_top {
    return [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeTop];
}
複製程式碼

實現為:

- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute;
複製程式碼
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
    return self;
}

- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [super init];
    if (!self) return nil;
    
    _view = view;
    _item = item;
    _layoutAttribute = layoutAttribute;
    
    return self;
}
複製程式碼

.with

我們繼續往下看的時候,.with 同樣是實現在 MASViewConstraint 中的一個方法:

- (MASConstraint *)with {
    return self;
}
複製程式碼

同樣是簡簡單單的返回了 self,而且是僅僅做了這一件事情。所以這個方法僅僅是一個語法……好吧都不能叫做語法糖,就叫語氣助詞吧,是為了讓我們寫出的 DSL 可讀性更高而存在的。當然了,你要是覺得多餘,也是可以不寫的。

.offset

接下來是 offset

- (MASConstraint * (^)(CGFloat offset))offset;
複製程式碼

就是簡簡單單的一個賦值操作罷了,寫成這麼複雜的原因就是實現可以傳遞引數的鏈式呼叫。

- (MASConstraint * (^)(CGFloat))offset {
    return ^id(CGFloat offset){
        self.offset = offset;
        return self;
    };
}
複製程式碼

以上就是 make.top.equalTo(superview.mas_top).with.offset(padding.top);Masonry 內部到底做了些什麼,其餘幾句也是類似的,總而言之就是:

MASConstraintMaker 型別的物件屬性(MASConstraint的子類) top(或其他任何你想要去佈局的屬性),進行了初始化,並通過返回 MASViewConstraint 型別的物件(newConstraint),不斷地呼叫 newConstraint 的物件方法,對 newConstraint 中的屬性做了賦值,以確保其可以完整的表達一個 NSLayoutConstraints

install

在配置好我們想要的約束後,我們還需要對檢視施加約束:

return [constraintMaker install];
複製程式碼

先來看一下 install 方法:

- (NSArray *)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;
}
複製程式碼

首先,會對 constraints 屬性做一份 copy,之後遍歷 constraints 中的所有 MASConstraint 及其子型別的屬性,並呼叫其 install 方法:

- (void)install;
複製程式碼

實現為:

- (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;
    }
    
    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];
    }
}
複製程式碼

接下來,我們來一段一段分析:

hasBeenInstalled

首先,在 install 之前,會做一次判斷,看是否已經被 install 過:

if (self.hasBeenInstalled) {
    return;
}
複製程式碼

判斷依據則是:

- (BOOL)hasBeenInstalled {
    return (self.layoutConstraint != nil) && [self isActive];
}
複製程式碼

layoutConstraint 是一個 MASLayoutConstraint 型別的 weak 屬性,MASLayoutConstraintNSLayoutConstraint 的子類,只是為了增加一個屬性(mas_key)。

@property (nonatomic, weak) MASLayoutConstraint *layoutConstraint;
複製程式碼

isActive 則是通過判斷 layoutConstraint 是否響應 isActive 以及 isActive 方法返回的結果,來綜合決定。

- (BOOL)isActive {
    BOOL active = YES;
    if ([self supportsActiveProperty]) {
        active = [self.layoutConstraint isActive];
    }

    return active;
}
複製程式碼
- (BOOL)supportsActiveProperty {
    return [self.layoutConstraint respondsToSelector:@selector(isActive)];
}
複製程式碼

mas_installedConstraints

if ([self supportsActiveProperty] && self.layoutConstraint) {
    self.layoutConstraint.active = YES;
    [self.firstViewAttribute.view.mas_installedConstraints addObject:self];
    return;
}
複製程式碼

如果 supportsActivePropertylayoutConstraint 不為空,則將 layoutConstraint.active 設為 YES,並將其新增到 firstViewAttribute.viewmas_installedConstraints 只讀屬性中去。

@property (nonatomic, readonly) NSMutableSet *mas_installedConstraints;
複製程式碼

mas_installedConstraints 是一個可變集合,是通過分類給 MAS_View 類新增的關聯物件,用來儲存已經 active 的物件。

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

生成約束

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

其實就是把我們再 block 中通過諸如 make.top.equalTo(superview.mas_top).with.offset(padding.top); 這樣的語句配置的屬性作為 MASLayoutConstraint 初始化方法的引數,生成一個約束。唯一需要注意的是,如果你設定的是一個 isSizeAttribute,並且 secondViewAttributenil,會做一些額外的引數調整。

施加約束

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

如果 secondViewAttribute.view 中的存在,就通過 mas_closestCommonSuperview 方法尋找最近的公共子檢視:

/**
 *	Finds the closest common superview between this view and another view
 *
 *	@param	view	other view
 *
 *	@return	returns nil if common superview could not be found
 */
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view;
複製程式碼
- (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;
}
複製程式碼

遞迴求解。

如果設定的是一個尺寸約束(firstViewAttribute.isSizeAttribute),則施加在 firstViewAttribute.view 上。否則施加在 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];
}
複製程式碼

對檢視施加約束,並將 layoutConstraint 儲存在 self.layoutConstraint 屬性中,同時把 self 儲存到之前提到過的叫做 mas_installedConstraints 的關聯物件中。至此,文章開始提到的例子業已完成。

原文地址:Masonry 原始碼解讀(上)

如果覺得我寫的還不錯,請關注我的微博@小橘爺,最新文章即時推送~

相關文章