源碼解讀——Masonry

原文連接ios

Masonry 概述

Masonry 是基於 Apple 的自動佈局封裝的一個輕量級佈局框架。Masonry 經過一種鏈式的 DSL(Domain-Specific Language)來描述 NSLayoutConstraint。相比原生的自動佈局語法,Masonry 提供了更爲簡便的語法來構造佈局。Masonry 同時支持 iOS 和 Mac OS X。git

關於原生的自動佈局的詳細內容,能夠閱讀另外一篇文章——《系統理解 iOS 自動佈局》github

本文所分析的 Masonry 源碼版本是 7.4.2數組

Auto Layout VS Masonry

蘋果提供的自動佈局(Auto Layout)可以對視圖進行靈活有效的佈局。可是,使用原生的自動佈局相關的語法建立約束的過程是很是冗長的,可讀性也比較差。緩存

以下所示代碼,其做用是讓一個子視圖填充其父視圖,其中子視圖的每一邊相對父視圖縮進 10 像素。安全

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

    // view1 constraints
    [NSLayoutConstraint constraintWithItem:view1
                                 attribute:NSLayoutAttributeTop
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:superview
                                 attribute:NSLayoutAttributeTop
                                multiplier:1.0
                                  constant:padding.top],

    [NSLayoutConstraint constraintWithItem:view1
                                 attribute:NSLayoutAttributeLeft
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:superview
                                 attribute:NSLayoutAttributeLeft
                                multiplier:1.0
                                  constant:padding.left],

    [NSLayoutConstraint constraintWithItem:view1
                                 attribute:NSLayoutAttributeBottom
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:superview
                                 attribute:NSLayoutAttributeBottom
                                multiplier:1.0
                                  constant:-padding.bottom],

    [NSLayoutConstraint constraintWithItem:view1
                                 attribute:NSLayoutAttributeRight
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:superview
                                 attribute:NSLayoutAttributeRight
                                multiplier:1
                                  constant:-padding.right],

 ]];
複製代碼

由上可見,使用原生的自動佈局語法,對於如此簡單的一個佈局,也是很是冗長的。若是使用 VFL(Visual Format Language)能夠有效減小冗餘,可是其 ASCII 類型語法使得編譯器沒法作類型檢查,存在必定的安全隱患。架構

Masonry 的目標其實就是 爲了解決原生自動佈局語法冗長的問題。對於上述示例,使用 Masonry 只須要一下幾行代碼便可解決。框架

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

甚至還能夠更加簡單:less

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(superview).with.insets(padding);
}];
複製代碼

Masonry 架構

基本組成

Masonry 主要方法由上述例子就可一窺全貌。Masonry 主要經過對 UIViewNSView)、NSArrayUIViewController 進行分類擴展,從而提供自動佈局的構建方法。相關方法定義在上圖所示部分文件中:ide

  • View+MASAddtions
  • NSArray+MASAddtions
  • ViewController+MASAddtions

經過分類提供的自動佈局構建方法主要有如下這些:

// View+MASAddtions
- (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;

// NSArray+Addtions
- (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;
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType withFixedItemLength:(CGFloat)fixedItemLength leadSpacing:(CGFloat)leadSpacing tailSpacing:(CGFloat)tailSpacing;
複製代碼

上述自動佈局構建方法均使用一個void(NS_NOESCAPE ^)(MASConstraintMaker *make) 類型的 block 做爲參數。的確,MASConstraintMaker 就是 Mansonry 框架中構建佈局約束的核心。MASConstraintMaker 引用了 MASConstraint 的一系列方法及其子類(包括:MASCompositeConstraintMASViewConstraint),從而實現約束的建立與添加。

MASConstraint 則提供了一系列返回類型爲 MASConstraint 的方法,從而實現了鏈式 DSL,使 Masonry 具有了簡潔靈活的優勢。

下面,咱們依次來介紹 Masonry 框架中的幾個重要類:

  • MASLayoutConstraint
  • MASViewAttribute
  • MASConstraint
  • MAConstraintMaker

MASLayoutConstraint

MASLayoutConstraint 類繼承自 NSLayoutConstraint 類。相比其父類,它就多了一個屬性 mas_key

MASLayoutConstraint 用來表示 佈局約束

MASViewAttribute

咱們知道在自動佈局系統中,約束的本質是一個方程式:

item1.attribute1 = multiplier × item2.attribute2 + constant
複製代碼

MASViewAttribute 就是約束方程式中一個 itemattribute 組成的單元。

以下所示即是 MASViewAttribute 定義的屬性。

@interface MASViewAttribute : NSObject

// The view which the reciever relates to. Can be nil if item is not a view.
@property (nonatomic, weak, readonly) MAS_VIEW *view;

// The item which the reciever relates to.
@property (nonatomic, weak, readonly) id item;

// The attribute which the reciever relates to
@property (nonatomic, assign, readonly) NSLayoutAttribute layoutAttribute;

@end
複製代碼

其中,關於 NSLayoutAttribute 枚舉所包含的類型,詳見 《系統理解 iOS 自動佈局》中 約束/約束規則/屬性 小節。

MASConstraint

MASConstraint 是一個抽象類,主要爲其子類 MASViewConstraintMASCompositeConstraint 聲明瞭一些共有的方法。MASConstraint 爲這些共有的方法實現了部分功能,底層的細節實現則由其子類決定。

根據約束方程式的組成,可將這些方法分爲如下幾類:

  • 屬性操做方法(Attribute)
  • 關係操做方法(Relationship)
  • 倍數操做方法(Multiplier)
  • 常量操做方法(Constant)

除此以外,還有優先級操做方法。

屬性操做方法

屬性操做方法根據對應的 NSLayoutAttribute 枚舉類型建立約束屬性項。

- (MASConstraint *)left;
- (MASConstraint *)top;
- (MASConstraint *)right;
- (MASConstraint *)bottom;
- (MASConstraint *)leading;
- (MASConstraint *)trailing;
- (MASConstraint *)width;
- (MASConstraint *)height;
- (MASConstraint *)centerX;
- (MASConstraint *)centerY;
- (MASConstraint *)baseline;

- (MASConstraint *)firstBaseline;
- (MASConstraint *)lastBaseline;

- (MASConstraint *)leftMargin;
- (MASConstraint *)rightMargin;
- (MASConstraint *)topMargin;
- (MASConstraint *)bottomMargin;
- (MASConstraint *)leadingMargin;
- (MASConstraint *)trailingMargin;
- (MASConstraint *)centerXWithinMargins;
- (MASConstraint *)centerYWithinMargins;
複製代碼

這些操做方法內部都是經過一個抽象方法實現,須由子類具體實現,該方法爲:

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute
複製代碼

關係操做方法

關係操做方法根據 NSLayoutRelation 枚舉類型建立約束關係項。

- (MASConstraint * (^)(id attr))equalTo;
- (MASConstraint * (^)(id attr))greaterThanOrEqualTo;
- (MASConstraint * (^)(id attr))lessThanOrEqualTo;
複製代碼

這些操做方法內部都是經過一個抽象方法實現,須由子類具體實現,該方法爲:

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation;
複製代碼

倍數操做方法

兩個倍數操做方法都是抽象方法,須由子類具體實現。

- (MASConstraint * (^)(CGFloat multiplier))multipliedBy;
- (MASConstraint * (^)(CGFloat divider))dividedBy;
複製代碼

常量操做方法

常量操做方法內部各自調用對應的 setter 方法,而這些 setter 方法都是抽象方法,須由子類具體實現。

- (MASConstraint * (^)(MASEdgeInsets insets))insets;
- (MASConstraint * (^)(CGFloat inset))inset;
- (MASConstraint * (^)(CGSize offset))sizeOffset;
- (MASConstraint * (^)(CGPoint offset))centerOffset;
- (MASConstraint * (^)(CGFloat offset))offset;
- (MASConstraint * (^)(NSValue *value))valueOffset;
複製代碼

優先級操做方法

後三個優先級操做方法根據 NSLayoutPriority 枚舉類型設置約束優先級,其內部都是經過調用第一個優先級操做方法實現的,該方法爲抽象方法,鬚子類具體實現。

- (MASConstraint * (^)(MASLayoutPriority priority))priority;
- (MASConstraint * (^)())priorityLow;
- (MASConstraint * (^)())priorityMedium;
- (MASConstraint * (^)())priorityHigh;
複製代碼

MASViewConstraint

MASViewConstraintMASConstraint 的子類,能夠稱之爲 Masonry 中 最重要的類

MASViewConstraint 除了可以 完整表示約束方程式 以外,還存儲了約束的 優先級 屬性。咱們來看一下其外部屬性和內部屬性。

// Public
@property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute;
@property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute;

// Private
@property (nonatomic, strong, readwrite) MASViewAttribute *secondViewAttribute;
@property (nonatomic, weak) MAS_VIEW *installedView;                // 約束被添加到的位置(視圖)
@property (nonatomic, weak) MASLayoutConstraint *layoutConstraint;  // 約束
@property (nonatomic, assign) NSLayoutRelation layoutRelation;      // 關係
@property (nonatomic, assign) MASLayoutPriority layoutPriority;     // 優先級
@property (nonatomic, assign) CGFloat layoutMultiplier;             // 倍數
@property (nonatomic, assign) CGFloat layoutConstant;               // 常量
@property (nonatomic, assign) BOOL hasLayoutRelation;
@property (nonatomic, strong) id mas_key;
@property (nonatomic, assign) BOOL useAnimator;
複製代碼

咱們再來看一下 MASViewConstraint 實現的父類抽象方法。

首先,屬性操做方法所調用的一個抽象方法。

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    // 必須是沒有設置過佈局關係,即 hasLayoutRelation 爲 NO
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
複製代碼

能夠看到,MASViewConstraint 其實將該方法的具體實現交給了它的代理。

其次,關係操做方法所調用的一個抽象方法。

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if ([attribute isKindOfClass:NSArray.class]) {
            // 必須是沒有設置過佈局關係,即 hasLayoutRelation 爲 NO
            NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
            // 若是 attribute 是一組屬性,則生成一組約束
            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");
            // 若是 attribute 是單個屬性,則設置約束的第二項
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}
複製代碼

能夠看到,針對 attribute 的不一樣,equalToWithRelation 方法實現了不一樣的邏輯。

接着,倍數操做方法所調用的兩個抽象方法。

- (MASConstraint * (^)(CGFloat))multipliedBy {
    return ^id(CGFloat multiplier) {
        NSAssert(!self.hasBeenInstalled,
                 @"Cannot modify constraint multiplier after it has been installed");
        self.layoutMultiplier = multiplier;
        return self;
    };
}

- (MASConstraint * (^)(CGFloat))dividedBy {
    return ^id(CGFloat divider) {
        NSAssert(!self.hasBeenInstalled,
                 @"Cannot modify constraint multiplier after it has been installed");
        self.layoutMultiplier = 1.0/divider;
        return self;
    };
}
複製代碼

能夠看到,這兩個方法本質上就是修改了 MASViewConstraint 的倍數屬性 layoutMultiplier

而後,常量操做方法所調用的幾個抽象方法。

// 只有約束方程式第一項的屬性是:
// NSLayoutAttributeLeft、NSLayoutAttributeLeading、
// NSLayoutAttributeTop、NSLayoutAttributeBottom、
// NSLayoutAttributeRight、NSLayoutAttributeTrailing 
// 時,方法纔會有效設置常量屬性
- (void)setInsets:(MASEdgeInsets)insets {
    NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
    switch (layoutAttribute) {
        case NSLayoutAttributeLeft:
        case NSLayoutAttributeLeading:
            self.layoutConstant = insets.left;
            break;
        case NSLayoutAttributeTop:
            self.layoutConstant = insets.top;
            break;
        case NSLayoutAttributeBottom:
            self.layoutConstant = -insets.bottom;
            break;
        case NSLayoutAttributeRight:
        case NSLayoutAttributeTrailing:
            self.layoutConstant = -insets.right;
            break;
        default:
            break;
    }
}

// setInsets 的特殊狀況
- (void)setInset:(CGFloat)inset {
    [self setInsets:(MASEdgeInsets){.top = inset, .left = inset, .bottom = inset, .right = inset}];
}

// 直接設置常量屬性
- (void)setOffset:(CGFloat)offset {
    self.layoutConstant = offset;
}

// 只有約束方程式第一項的屬性是:
// NSLayoutAttributeWidth、NSLayoutAttributeHeight
// 時,方法纔會有效設置常量屬性
- (void)setSizeOffset:(CGSize)sizeOffset {
    NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
    switch (layoutAttribute) {
        case NSLayoutAttributeWidth:
            self.layoutConstant = sizeOffset.width;
            break;
        case NSLayoutAttributeHeight:
            self.layoutConstant = sizeOffset.height;
            break;
        default:
            break;
    }
}

// 只有約束方程式第一項的屬性是:
// NSLayoutAttributeCenterX、NSLayoutAttributeCenterY
// 時,方法纔會有效設置常量屬性
- (void)setCenterOffset:(CGPoint)centerOffset {
    NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;
    switch (layoutAttribute) {
        case NSLayoutAttributeCenterX:
            self.layoutConstant = centerOffset.x;
            break;
        case NSLayoutAttributeCenterY:
            self.layoutConstant = centerOffset.y;
            break;
        default:
            break;
    }
}
複製代碼

能夠看到,這些 setter 方法會根據 MASViewConstraint 已有的 firstViewAttribute 約束項的約束屬性 layoutAttribuet 的類型來設置常量屬性。當屬性不匹配值,對常量屬性的設置並不會生效。

最後,優先級操做方法的一個抽象方法。

- (MASConstraint * (^)(MASLayoutPriority))priority {
    return ^id(MASLayoutPriority priority) {
        NSAssert(!self.hasBeenInstalled,
                 @"Cannot modify constraint priority after it has been installed");
        self.layoutPriority = priority;
        return self;
    };
}
複製代碼

能夠看到,該方法內部直接設置了 MASViewConstraint 的優先級屬性 layoutPriority

MASCompositeConstraint

MASCompositeConstraint 也是 MASConstraint 的子類。與 MASViewConstraint 只表示一個約束不一樣,MASCompositeConstraint 能夠表示一組約束。

@interface MASCompositeConstraint () <MASConstraintDelegate>

@property (nonatomic, strong) id mas_key;
@property (nonatomic, strong) NSMutableArray *childConstraints;

@end
複製代碼

其中,childConstraints 屬性持有了一組約束。

咱們再來看一下 MASCompositeConstraint 實現的父類抽象方法。

首先,屬性操做方法所調用的一個抽象方法。

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    [self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
    return self;
}
複製代碼

該方法調用了 MASCompositeConstraint 所實現的 MASConstraintDelegate 的一個方法。

- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    id<MASConstraintDelegate> strongDelegate = self.delegate;
    MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
    newConstraint.delegate = self;
    [self.childConstraints addObject:newConstraint];
    return newConstraint;
}
複製代碼

能夠看出,該方法內部將經過其代理新建立的普通約束或組合約束添加至 MASCompositeConstraintchildConstraints 數組中,並設置子約束的代理爲 MASCompositeConstraint 的代理。

事實上,在 Masonry 中,下文將要提到的 MASConstraintMaker 充當了全部約束的最終代理,以下圖所示。MASCompositeConstraint 只是充當了轉接和補充的做用。

至於關係操做方法、倍數操做方法、常量操做方法、優先級操做方法所調用的抽象方法。MASCompositeConstraint 對此的實現基本相同,都是對 childConstraints 中的約束進行遍歷設置。

MASConstraintMaker

MASConstraintMaker 是 Masonry 的核心。

MASConstraintMaker 指定了構建佈局的目標視圖以及相關的約束。

@interface MASConstraintMaker () <MASConstraintDelegate>

@property (nonatomic, weak) MAS_VIEW *view;
@property (nonatomic, strong) NSMutableArray *constraints;

@end
複製代碼

MASConstraintMaker 提供了一系列只讀的 MASConstraint 屬性。這些屬性在其 getter 方法內建立了對應 NSLayoutAttribute 枚舉類型的約束項。這些屬性包括如下:

@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
@property (nonatomic, strong, readonly) MASConstraint *leading;
@property (nonatomic, strong, readonly) MASConstraint *trailing;
@property (nonatomic, strong, readonly) MASConstraint *width;
@property (nonatomic, strong, readonly) MASConstraint *height;
@property (nonatomic, strong, readonly) MASConstraint *centerX;
@property (nonatomic, strong, readonly) MASConstraint *centerY;
@property (nonatomic, strong, readonly) MASConstraint *baseline;

@property (nonatomic, strong, readonly) MASConstraint *firstBaseline;
@property (nonatomic, strong, readonly) MASConstraint *lastBaseline;

@property (nonatomic, strong, readonly) MASConstraint *leftMargin;
@property (nonatomic, strong, readonly) MASConstraint *rightMargin;
@property (nonatomic, strong, readonly) MASConstraint *topMargin;
@property (nonatomic, strong, readonly) MASConstraint *bottomMargin;
@property (nonatomic, strong, readonly) MASConstraint *leadingMargin;
@property (nonatomic, strong, readonly) MASConstraint *trailingMargin;
@property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;
@property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;

@property (nonatomic, strong, readonly) MASConstraint *edges;
@property (nonatomic, strong, readonly) MASConstraint *size;
@property (nonatomic, strong, readonly) MASConstraint *center;

@property (nonatomic, strong, readonly) MASConstraint *(^attributes)(MASAttribute attrs);
複製代碼

上面提到,MASViewConstraintMASCompositeConstraint 都會利用其代理來建立並添加約束項,而它們的代理都是 MASConstraintMaker。那麼,咱們來看一下 MASConstraintMaker 對於 MASConstraintDelegate 的實現是怎麼樣的。

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

- (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]) {
        // 若是是在已有約束的基礎上再建立的約束,則將它們轉換成一個 組合約束,並將原約束替換成該組合約束。
        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;
}

複製代碼

咱們先看 constraint:shouldBeReplacedWithConstraint: 方法,該方法的職責很是簡單,就是在已有的約束中查找某個約束並進行替換。

咱們再看constraint:addConstraintWithLayoutAttribute: 方法,該方法是被調用較多的一個方法,其職責主要就是建立並添加約束至 constraints 列表屬性中。

工做流程

在瞭解了 Masonry 的基本組成以後,咱們再經過一個示例來介紹一下 Masonry 的工做流程。

示例以下所示。

[view mas_makeConstraints::^(MASConstraintMaker *make) {
    make.top.equalTo(@10);
    make.left.equalTo(superview.mas_left).offset(10);
    make.width.height.equalTo(@100);
}];
複製代碼

首先執行分類方法 mas_makeConstraints:

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

方法內部先設置 translatesAutoresizingMaskIntoConstraintsNO。由於,Autoresize Mask 和 Auto Layout 是兩套佈局系統,前者默承認以轉換成後者。爲了不前者對自動佈局系統產生干擾,這裏須要關閉佈局轉換。

方法內部還會建立一個 MASConstraintMaker 實例,而後以此爲參數調用 block 執行。

constraintMaker 建立完約束後,在調用 install 方法將約束添加至正確的約束層級位置。install 方法的內部實現以下:

- (NSArray *)install {
    // 只有在 mas_remakeConstraints 時,removeExisting 才爲 YES
    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) {
        // 設置約束的 updateExisting 屬性
        // 只有在 mas_updateConstraints 時,updateExisting 才爲 YES
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
    // 清空 constraints 數組緩存
    [self.constraints removeAllObjects];
    return constraints;
}
複製代碼

install 方法內部會對 constraints 列表中的全部約束依次執行各自的 install 方法來添加約束。咱們來看一下約束的 install 方法

// MASCompositeConstraint
- (void)install {
    for (MASConstraint *constraint in self.childConstraints) {
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
}

// MASViewConstraint
- (void)install {
    // 約束是否已被添加
    if (self.hasBeenInstalled) {
        return;
    }
    
    // 若是約束支持 isActive 方法,且 self.layoutConstraint 有值了
    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
    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;
    
    // 肯定約束layoutConstraint 的約束層級(即要被添加到的位置)
    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
        // 約束存在,則更新constant值
        existingConstraint.constant = layoutConstraint.constant;
        self.layoutConstraint = existingConstraint;
    } else {
        // 約束不存在,則在該位置添加約束
        [self.installedView addConstraint:layoutConstraint];
        self.layoutConstraint = layoutConstraint;
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}
複製代碼

不管是 MASCompositeConstraint 仍是 MASViewConstraint,本質上仍是調用 MASViewConstraintinstall 方法。該方法根據 MASViewConstraint 的各個屬性建立一個原生的約束(NSLayoutConstraint 類型),並在定位約束層級後,將約束添加到相應層級的視圖上。

下面,咱們再來看看執行 block 又發生了什麼。

首先,看一下 make.top.equalTo(@10); 的執行流程。

// MASConstraintMaker
- (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]) {
        // ...
    }
    if (!constraint) {
        // 若是不是在已有約束的基礎上再建立約束,則添加約束至列表
        newConstraint.delegate = self;      // 注意這一步,會對 make.top.left 這種情形產生關鍵影響,詳見下文
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

// -----------------------------------------------------------------
// 至此,make.top 執行完畢
// -----------------------------------------------------------------

// MASConstraint
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        // attribute 多是 @0 相似的值,也多是 view.mas_width等這樣的
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

// MASViewConstraint
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if ([attribute isKindOfClass:NSArray.class]) {
            // ...
        } else {
            NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;   // 設置約束第二項
            return self;
        }
    };
}

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

// MASConstraint
- (void)setLayoutConstantWithValue:(NSValue *)value {
    if ([value isKindOfClass:NSNumber.class]) {
        self.offset = [(NSNumber *)value doubleValue];
    } else if (strcmp(value.objCType, @encode(CGPoint)) == 0) {
        // CGPoint point;
        // [value getValue:&point];
        // self.centerOffset = point;
    } else if (strcmp(value.objCType, @encode(CGSize)) == 0) {
        // CGSize size;
        // [value getValue:&size];
        // self.sizeOffset = size;
    } else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) {
        // MASEdgeInsets insets;
        // [value getValue:&insets];
        // self.insets = insets;
    } else {
        // NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value);
    }
}

// MASViewConstraint
- (void)setOffset:(CGFloat)offset {
    self.layoutConstant = offset;       // 設置約束常量
}

// -----------------------------------------------------------------
// 至此,make.top.equalTo(@10) 執行完畢
// -----------------------------------------------------------------

複製代碼

而後,咱們再看 make.left.equalTo(superview.mas_left).offset(10); 的執行流程。 其實,這個執行流程也就是執行 equalTo 內部的 setSecondViewAttribute 時有所不一樣。另外,offset 方法作了一步額外的操做。

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

// -----------------------------------------------------------------
// 至此,make.left.equalTo(superview.mas_left) 執行完畢
// -----------------------------------------------------------------

// MASConstraint
- (MASConstraint * (^)(CGFloat))offset {
    return ^id(CGFloat offset){
        self.offset = offset;
        return self;
    };
}

- (void)setOffset:(CGFloat)offset {
    self.layoutConstant = offset;
}

// -----------------------------------------------------------------
// 至此,make.left.equalTo(superview.mas_left).offset(10) 執行完畢
// -----------------------------------------------------------------
複製代碼

最後,咱們再看 make.width.height.equalTo(@100); 的執行流程。 其實到 make.width 這一步與前面沒有什麼差異,再執行 height 時出現了轉換。

// MASConstraint
- (MASConstraint *)height {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}

// MASViewConstraint
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
    // 見上述 make.top.equalTo(@10) 分析代碼中的介紹,此時 self.delegate 早已被設置成了 NSConstraintMaker 了
    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}

// MASConstraintMaker
- (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]) {
        // 若是是在已有約束的基礎上再建立的約束,則將它們轉換成一個 組合約束,並將原約束替換成該組合約束。
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        // 這裏會將原來 make.width 添加的約束 替換成一個 組合約束(寬度約束 + 高度約束)
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        // 返回組合約束
        return compositeConstraint;
    }
    if (!constraint) {
        // ...
    }
    // ...
}

- (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.width.height 執行完畢
// -----------------------------------------------------------------

// MASConstraint
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        // attribute 多是 @0 相似的值,也多是 view.mas_width等這樣的
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

// MASCompositeConstraint
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attr, NSLayoutRelation relation) {
        // CompositeConstraint 的 childConstraits 中每一項,調用 equalToWithRelation
        for (MASConstraint *constraint in self.childConstraints.copy) {
            constraint.equalToWithRelation(attr, relation);
        }
        return self;
    };
}

// -----------------------------------------------------------------
// 至此,make.width.height.equalTo(@100) 執行完畢
// -----------------------------------------------------------------
複製代碼

總結

Masonry 巧妙利用了面向對象的繼承、多態思想以及 block 的特性,從而實現了很是簡便的鏈式 DSL,極大地提高了自動佈局開發的效率。

參考

  1. Masonry

(完)

相關文章
相關標籤/搜索