Masonry 源码解读(上)

前言

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 源码解读(上)

如果觉得我写的还不错,请关注我的微博@小橘爷,最新文章即时推送~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容