Masonry源代碼解析

前言

CSDN地址:http://blog.csdn.net/game3108/article/details/52473932
本文的中文注釋代碼demo更新在我的github上。

AutoLayout是Apple在iOS6中新增的UI布局適配的方法,用來替代iOS6之前的AutoResizeing。AutoLayout對應的代碼約束就是NSLayoutConstraint。NSLayoutConstraint的API雖說時分簡單,但約束的代碼量較大,所以出現了很多對NSLayoutConstraint的封裝,今天講的就是其中最為有名的Masonry框架。

Masonry框架簡化了約束NSLayoutConstraint的寫法,在各種APP中都有很多的使用。Masonry是基于Objective-C語言的框架,Swift項目可以參考Snapkit框架。

本文會基于Masonry v1.0.1版本,同時借鑒了網上很多解析文章,對源代碼進行解析,進行學習。


16.9.14更新:
MASViewConstraint的equalToWithRelation添加array缺少//viewConstraint.layoutRelation = relation;的pull request已經被接受,該問題fixed。

約束

NSLayoutConstraint約束是基于以下公式:

item1.attribute1 = multiplier × item2.attribute2 + constant

比如button1的左側距離button2有10的約束會這么寫:

button1.left = button2.right + 10;

事實上添加NSLayoutConstraint約束有三種方式:

  • 1.storyboard/xib添加
  • 2.VFL語言添加
  • 3.NSLayoutConstraint純代碼添加

1.storyboard/xib添加

storyboard/xib添加NSLayoutConstraint的方式主要是右下角的約束設置:

xib右下角

分別是:

  • Align
  • Pin
  • Resolve Auto Layout Issues

相應的圖這里就不再貼了,有興趣的可以自己去試一下

2.VFL語言添加

VFL(Visual Format Language)是蘋果公司為了簡化autolayout的編碼而推出的抽象語言。
VFL調用以下方法:

+ (NSArray<__kindof NSLayoutConstraint *> *)constraintsWithVisualFormat:(NSString *)format 
options:(NSLayoutFormatOptions)opts 
metrics:(nullable NSDictionary<NSString *,id> *)metrics 
views:(NSDictionary<NSString *, id> *)views;

其中的format就是vfl語句。
vfl的語句也較為復雜,這里不詳細介紹了,具體可以參考蘋果文檔Visual Format Language

3.NSLayoutConstraint純代碼添加

我們舉一個例子:
view的上部距離距離superview有10的距離:

    NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view                        //view
                                                                  attribute:NSLayoutAttributeTop        //view.top
                                                                  relatedBy:NSLayoutRelationEqual       //view.top =
                                                                     toItem:superView                   //view.top = superView
                                                                  attribute:NSLayoutAttributeTop        //view.top = superView.top
                                                                 multiplier:1.0                         //view.top = 1.0 * superView.top
                                                                   constant:10.0];                      //view.top = 1.0 * superView.top + 10
    [view addConstraint:constraint];

可以看到約束代碼算是列出了一個約束公式,也達到了約束的目的。
但這些代碼,其實只寫了一個top的約束,如果有其它約束代碼,需要同樣寫類似的代碼出來,所以直接用NSLayoutConstraint純代碼添加還是比較麻煩的一件事。

4.約束的限制

(1)對于兩個同層級 view 之間的約束關系,添加到它們的父 view 上
(2)對于兩個不同層級 view 之間的約束關系,添加到他們最近的共同父 view 上
(3)對于有層次關系的兩個 view 之間的約束關系,添加到層次較高的父 view 上
(4)對于比如長寬之類的,只作用在該 view 自己身上的話,添加到該 view 自己上

Masonry的使用

1.Masonry的例子

Masonry的代碼封裝了NSLayoutConstraint純代碼,簡潔了許多,和NSLayoutConstraint純代碼舉同一個例子:
view的上部距離距離superview有10的距離

    [view mas_makeConstraints:^(MASConstraintMaker *make){
        make.top.equalTo(10);  //默認是父view 三者等價  view.top = 1.0 * superView.top + 10
        
        make.top.mas_equalTo(superView.mas_top).with.multipliedBy(1.0).mas_offset(10);
        
        make.top.equalTo(superView.top).offset(10);
    }];

相對于NSLayoutConstraint純代碼的添加約束,Masonry使用了block外加鏈式語法,使得調用簡潔和方便了許多。

Masonry源代碼

1.整體結構

Masonry的目錄結構如下:

Masonry

文件比較多,借用iOS開發之Masonry框架源碼深度解析的類圖:

iOS開發之Masonry框架源碼深度解析-Masonry類圖

根據類圖,文件目錄主要分為以下幾類:

  • 1.頭文件和輔助文件
    Masonry.h:頭文件
    MASUtilities.h:定義宏MASBoxValue,轉換類型
    UIView+MASShorthandAdditions.h:定義UIView調用的簡化參數和方法
    NSArray+MASShorthandAdditions.h:定義NSArray調用的簡化參數和方法
    NSLayoutConstraint+MASDebugAdditions.h/m:DEBUG相關信息轉換

  • 2.約束使用接口
    UIView+MASAdditions.h/m:UIView的約束接口
    UIViewController+MASAdditions.h/m:UIViewController的約束借口
    NSArray+MASAdditions.h/m:遍歷NSArray中的UIView的約束接口

  • 3.約束建造者builder模式
    MASConstraintMaker.h/m:約束構造使用的建造者builder

  • 4.約束內部結構和實現
    MASLayoutConstraint.h/m:NSLayoutConstraint多了一層key的封裝
    MASAttribute.h:NSLayoutAttribute的一層封裝
    MASConstraint.h/m:定義鏈式結構體的抽象父類
    MASConstraint+Private.h:定義MASConstraint的接口和delegate代理
    MASViewConstraint.h/m:繼承MASConstraint,表示view的約束結構體
    MASCompositeConstraint.h/m:繼承MASConstraint,表示系列view組合的約束結構體

看了源代碼會發現,Masonry的代碼流程簡單來講就是:提供給用戶一個建造者MASConstraintMaker,讓用戶根據mansory提供的語法,添加約束結構體MASConstraint。最后Masonry解析約束結構體MASConstraint,將真正的約束關系NSLayoutConstraint添加到相應的view上。

2.源代碼探究

我們根據一個實際調用代碼來講Masonry的源代碼。代碼如下:

[view mas_makeConstraints:^(MASConstraintMaker *make){
        make.top.mas_equalTo(superView.mas_top).with.mas_offset(10);
}];

這邊其實主要分兩塊

  • MASConstraintMaker的鏈式調用
  • view的約束函數調用

在講這兩塊前,首先講一下底層的數據結構MASConstraint相關的結構

(1)MASConstraint相關結構

MASConstraint是定義約束的抽象類(雖然OC沒有抽象類的說法)
定義大概如下:

@interface MASConstraint : NSObject
...
- (MASConstraint * (^)(CGFloat offset))offset;
...
- (MASConstraint *)left;
- (MASConstraint *)top;
...
- (void)install;
- (void)uninstall;

這邊只選取了部分方法和參數,來看一下相應的實現

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


//讓子類去重寫該方法,父類拋出異常達到抽象類的效果
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
    MASMethodNotImplemented();
}

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

- (void)install { MASMethodNotImplemented(); }

- (void)uninstall { MASMethodNotImplemented(); }

可以看到這邊的實現達到了以下目的:

  • 1.鏈式調用的實現
    為了實現鏈式調用,并且可以用小括號"()"加入參數,而不是中括號"[]",這邊采用了返回同樣類型的block的方式
  • 2.讓子類實現方法,父類作為抽象類存在
    雖然oc沒有抽象類的定義,但在MASConstraint定義了一系列方法,讓子類進行重寫,而父類則用宏MASMethodNotImplemented()拋出異常

而MASConstraint的子類則分別是MASViewConstraint與MASCompositeConstraint

MASViewConstraint

MASViewConstraint定義著一個單獨的約束關系
MASViewConstraint.h中定義如下:

@interface MASViewConstraint : MASConstraint <NSCopying>
//代表第一個item和相應的attribute
@property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute;
//代表第二個item和相應的attribute
@property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute;
...

這里又多了一個類MASViewAttribute

@interface MASViewAttribute : NSObject
//調用view
@property (nonatomic, weak, readonly) MAS_VIEW *view;
//交互對象
@property (nonatomic, weak, readonly) id item;
//約束NSLayoutAttribute類型
@property (nonatomic, assign, readonly) NSLayoutAttribute layoutAttribute;

MASViewAttribute比較好理解,其實就是一個調用view,交互的item和NSLayoutAttribute的結構體
而為什么在MASViewConstraint中會有兩個MASViewAttribute?
我們再看一下一個標準的約束調用代碼:

 NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view                        //view
                                                                  attribute:NSLayoutAttributeTop        //view.top
                                                                  relatedBy:NSLayoutRelationEqual       //view.top =
                                                                     toItem:superView                   //view.top = superView
                                                                  attribute:NSLayoutAttributeTop        //view.top = superView.top
                                                                 multiplier:1.0                         //view.top = 1.0 * superView.top
                                                                   constant:10.0];                      //view.top = 1.0 * superView.top + 10

可以看到,一個約束必須要有兩組<item,NSLayoutAttribute>的結構體,還需要NSLayoutRelation\multiplier\constant。而這兩個MASViewAttribute就是對應兩組兩組<item,NSLayoutAttribute>的結構體。

MASCompositeConstraint

MASCompositeConstraint代表一組MASViewConstraint約束
比如源代碼中的make.top會轉化為MASViewConstraint約束,而make.top.bottom.left.right.xxxxx這種寫法會定義多種MASViewConstraint約束,而為了存儲這種寫法,則創建了MASCompositeConstraint。

MASCompositeConstraint.h定義并沒有暴露太多細節,細節都在.m的匿名extension中:

@interface MASCompositeConstraint () <MASConstraintDelegate>
@property (nonatomic, strong) id mas_key;
//存儲內部結構體,都是MASViewConstraint
@property (nonatomic, strong) NSMutableArray *childConstraints;
@end

childConstraints中存儲的是每一條MASViewConstraint約束

(2)MASConstraintMaker調用

MASConstraintMaker匿名Extension中的定義

@interface MASConstraintMaker () <MASConstraintDelegate>
@property (nonatomic, weak) MAS_VIEW *view;
//存儲MASConstraint
@property (nonatomic, strong) NSMutableArray *constraints;
@end

可以看到在有一個MASConstraintMaker中專門有一個@property (nonatomic, strong) NSMutableArray *constraints;去存儲所有的MASConstraint對象。就是你有好幾行的make.xxx都會存儲在這里。

MASConstraintMaker.h中添加了許多的MASConstraint property

@interface MASConstraintMaker : NSObject
//property 重寫了getter方法,使得每次鏈式調用相當于構造一個約束
@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
...

并重寫了它們的getter方法:

//通用增加約束的方法
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

//重寫getter方法,調用.方法相當于構造約束
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

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

相當于我make調用相應的property,就是給給view添加了某個NSLayoutAttribute的結構體

//替換函數
- (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];
}

//通過NSLayoutAttribute添加約束
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    //構造view的MASViewAttribute
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    //通過MASViewAttribute構造第一個MASViewConstraint
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    //如果存在contraint,則把constraint和newConstraint組合成MASCompositeConstraint
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        //replace with composite constraint
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        //替換原來的constraint成新的MASCompositeConstraint
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    //不存在則設置constraint到self.constraints
    if (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

舉一個實際的例子:

make.top.mas_equalTo(superView.mas_top).with.mas_offset(10);

當調用make.top的時候就會創建一個只只有firstViewAttribute的MASViewConstraint對象,并且進入不存在約束constraint的代碼部分

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

設置delegate,并且將約束添加到self.constraints,同時返回的是剛剛創建的MASViewConstraint對象。

如果調用代碼是make.top.left到left的時候其實是MASViewConstraint對象.left的調用,會走到我們剛才說的MASViewConstraint中重寫的- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute方法,實現如下:

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的delegate方法,而MASViewConstraint的delegate對象則是剛才設置過的MASConstraintMaker對象,回到了剛才添加約束的方法。又新建了一個MASViewConstraint對象,并且因為constraint不是nil,進入

if ([constraint isKindOfClass:MASViewConstraint.class]) {
        //replace with composite constraint
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        //替換原來的constraint成新的MASCompositeConstraint
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }

返回的是一個MASCompositeConstraint對象。其中注意MASCompositeConstraint的初始化方法

- (id)initWithChildren:(NSArray *)children {
    self = [super init];
    if (!self) return nil;

    _childConstraints = [children mutableCopy];
    for (MASConstraint *constraint in _childConstraints) {
        constraint.delegate = self;
    }

    return self;
}

會把里面的MASViewConstraint的delegate全部設置到MASCompositeConstraint對象身上。

如果調用的代碼是make.top.left.right到right的時候,就是MASCompositeConstraint對象.right的調用,會走MASCompositeConstraint中的重寫方法

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

這邊也會調用MASConstraintMaker的同一個方法,但這次if ([constraint isKindOfClass:MASViewConstraint.class])if (!constraint)都不會進入,只會返回單純的MASViewConstraint對象,然后設置它的delegate,并且將對象存入MASCompositeConstraint的childConstraints中。
之后再有更多的鏈式MASConstraint的組合,都是MASCompositeConstraint的調用,不停的加入childConstraints中而已。同理superView.mas_top的構成也是同樣的方式。

直到調用到mas_equalTo(xx)
在MASConstraint中的實現是:

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

兩個子類重寫了equalToWithRelation該方法
MASViewConstraint

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        //如果是array,則是代表是多個組合對象
        if ([attribute isKindOfClass:NSArray.class]) {
            //如果已經有relation,則報錯
            NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
            //組合約束存儲
            NSMutableArray *children = NSMutableArray.new;
            //遍歷每一個組合對象
            for (id attr in attribute) {
                //復制自己,這里有個邏輯,因為在copy的時候會設置一下layoutRelation,這邊的setLayoutRelation
                //方法會把hasLayoutRelation設置為YES,單layoutRelation是默認的NSLayoutRelationEqual
                //這邊其實比較明顯有個bug,就是沒有將relation在這里設置一下,所以所有的關系都會變成默認的
                //我提交了一個pull request,等待修改
                MASViewConstraint *viewConstraint = [self copy];
                //viewConstraint.layoutRelation = relation;
                //設置設置second attribute
                viewConstraint.secondViewAttribute = attr;
                [children addObject:viewConstraint];
            }
            //初始化MASCompositeConstraint
            MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
            compositeConstraint.delegate = self.delegate;
            //替換原來的位置
            [self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
            return compositeConstraint;
        } else {
            //判斷是否已經有過relation,有的話則報錯
            NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
            //存儲relation
            self.layoutRelation = relation;
            //設置second attribute
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}

tips:這邊的代碼有array添加的時候,會有一個relation設置問題的bug,少了//viewConstraint.layoutRelation = relation;這句話。我已經提交了pull request,等待修復該問題。

MASCompositeConstraint

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attr, NSLayoutRelation relation) {
        //遍歷所有的MASViewConstraint并設置
        for (MASConstraint *constraint in self.childConstraints.copy) {
            constraint.equalToWithRelation(attr, relation);
        }
        return self;
    };
}

可以看到,該方法主要是為了設置layoutRelation與secondViewAttribute。
最后則是調用

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

設置偏移。
至此,整個構建創建者MASConstraintMaker的調用到此為止。

(3)約束函數調用

例子代碼中的調用函數如下:
UIView+MASAdditions.m

//創建約束
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    //去掉自動autoresizing轉為約束的
    self.translatesAutoresizingMaskIntoConstraints = NO;
    //構建builder
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    //運行builder
    block(constraintMaker);
    //附值約束返回
    return [constraintMaker install];
}

先是去掉了AutoResizing的自動轉換,然后創建一個MASConstraintMaker對象,調用block去構成builder建造者。最后是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) {
        //設置更新key
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
    //去除所有緩存的約束結構體
    [self.constraints removeAllObjects];
    return constraints;
}

先判斷是否刪除所有的約束,然后再添加需要更新的約束。

先看一下uninstall方法
MASCompositeConstraint.m

- (void)uninstall {
    for (MASConstraint *constraint in self.childConstraints) {
        [constraint uninstall];
    }
}

MASConstraint.m

//刪除約束
- (void)uninstall {
    //判斷是否支持active去設置NSLayoutConstraint
    if ([self supportsActiveProperty]) {
        //設置active為no
        self.layoutConstraint.active = NO;
        //刪除installed緩存
        [self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
        return;
    }
    
    //刪除此約束
    [self.installedView removeConstraint:self.layoutConstraint];
    self.layoutConstraint = nil;
    self.installedView = nil;
    //刪除installed緩存
    [self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
}

其中NSLayoutConstraint的active相當于addConstraint:removeConstraint:

然后就是install方法
MASCompositeConstraint.m

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

MASViewConstraint.m

- (void)install {
    //已經有約束
    if (self.hasBeenInstalled) {
        return;
    }
    
    //支持active且已經有了約束
    if ([self supportsActiveProperty] && self.layoutConstraint) {
        //激活約束
        self.layoutConstraint.active = YES;
        //添加約束緩存
        [self.firstViewAttribute.view.mas_installedConstraints addObject:self];
        return;
    }
    
    //獲得item1,attribute1,item2,attribute2
    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)
    //如果attribute是sizeattribute并且沒有第二個attribute
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        //默認設置item2為superview
        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];
    
    //設置priority和mas_key
    layoutConstraint.priority = self.layoutPriority;
    layoutConstraint.mas_key = self.mas_key;
    
    //如果第二個attribute有view對象
    if (self.secondViewAttribute.view) {
        //則獲取兩個view的最小公共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);
        //設置約束view為此view
        self.installedView = closestCommonSuperview;
    } else if (self.firstViewAttribute.isSizeAttribute) {
        //如果是size attribute則為本身
        self.installedView = self.firstViewAttribute.view;
    } else {
        //其它則是superview
        self.installedView = self.firstViewAttribute.view.superview;
    }

    //已經存在的約束
    MASLayoutConstraint *existingConstraint = nil;
    //需要更新
    if (self.updateExisting) {
        //則獲得此生成的約束,返回和installedview的約束是同類的約束
        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];
    }
}

其中比較有趣的是取得兩個view的最小公共view的方法mas_closestCommonSuperview:

至此,簡單的Masonry主調用的調用源代碼也算全部解析過了。

總結

總的來說Masonry的源代碼有以下優點:

  • 1.大量簡潔優美的宏(雖然用宏好不好另說)
  • 2.鏈式調用的實現
  • 3.builder建造者模式構成約束類型和設置約束
  • 4.NSAssert語句判斷類型
  • 5.構造抽象類MASConstraint(雖然oc沒有抽象類)
  • 6.內部接口MASConstraint+Private.h

參考資料

1.Apple-NSLayoutConstraint
2.Auto Layout Guide
3.Visual Format Language
3.深入剖析Auto Layout,分析iOS各版本新增特性
4.iOS開發之Masonry框架源碼深度解析
5.Masonry源代碼分析
6.史上比較用心的純代碼實現 AutoLayout

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容