Masonry源码剖析

版别:v1.1.0

github链接

引子

首要咱们依据较为完好的运用,与NSLayoutConstraints作对比,然后再逐步剖析Masonry完成主动布局的过程。

    UIView *superview = self.view;
  UIView *view1 = [[UIView alloc] init];
  view1.backgroundColor = UIColor.redColor;
   [superview addSubview:view1];
  
  //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);
   }];
  
  //NSLayoutConstraint布局
   [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],
   ]];

从布局代码量能够看出,运用Masonry布局是简洁很多的,可读性也较高。

当然,Masonry是经过链式调用的方式简化了代码,实践上最后也是转化为NSLayoutConstraint布局(下文会具体剖析)。

剖析过程

咱们从上面的mas_makeConstraints布局视点动身,剖析Masonry怎么做到如此简洁地完结一个view的束缚。主要剖析过程为

一、给谁做束缚

二、怎么组成束缚

三、怎么完结束缚

一、给谁做束缚

经过检查mas_makeConstraints办法的定义可知,束缚目标类型为MAS_VIEW,在MASUtilities.h文件中能够找到其宏定义。

#if TARGET_OS_IPHONE || TARGET_OS_TV
  #define MAS_VIEW UIView
#elif TARGET_OS_MAC
  #define MAS_VIEW NSView
#endif

这儿咱们仅针对iOS架构来剖析,能够得出:是给UIView做束缚

二、怎么组成束缚

咱们先来剖析这一句代码,研讨各个链式调用是怎么终究组成一句束缚的,咱们能够先从make下手

make.left.equalTo(superview.mas_left).with.offset(padding.left);

(1)make是何物?

mas_makeConstraints调用得知,make为MASConstraintMaker类型目标,能够理解为帮忙创立束缚的类。那其对应的topleftright等等特点是什么概念呢?

(2)MASConstraintMaker的topleft等特点

① 检查定义得知,这些特点定位在MASConstraintMaker类中,回来了MASConstraint类型

  @property (nonatomic, strong, readonly) MASConstraint *left;
  - (MASConstraint *)left {
   return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
  }
 
 - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
  return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
  }

②以left为例,实践的操作为,构造一个MASViewConstraint(继承自MASConstraint)目标并回来,其firstViewAttributeitem为当前做束缚的view,layoutAttribute为对应的NSLayoutAttributeLeft

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

如上咱们得到MASConstraint类型目标,能够算是承认了需求创立束缚的具体特点,然后将该束缚增加到constraints数组特点中。

接下来便是equalTo(superview.mas_left).with.offset(padding.left);操作,咱们先来研讨MASConstraintequalTo(...) / mas_equalTo(...)操作。

(3)MASViewConstraint的equalTo(...) / mas_equalTo(...)办法

#define mas_equalTo(...)         equalTo(MASBoxValue((__VA_ARGS__)))
#define equalTo(...)           mas_equalTo(__VA_ARGS__)
​
- (MASConstraint * (^)(id))mas_equalTo {
  return ^id(id attribute) {
    return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
   };
}
​
//MASViewConstraint的equalToWithRelation办法
- (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;
     }
   };
}

该办法回来了一个block,传入attribute回来MASConstraint类型目标。

此办法承认了MASViewConstraint的layoutRelation为NSLayoutRelationEqual, 而传入的参数(superview.mas_left)即为secondViewAttribute

(4)MASViewConstraintwith/and办法

- (MASConstraint *)with {
  return self;
}
​
- (MASConstraint *)and {
  return self;
}

这两个办法回来了self,其实的起到了连接语义的作用,一般能够省掉。接下来咱们看看offset办法

(5)MASViewConstraintoffset(...)/mas_offset(...)办法

#define mas_offset(...)          valueOffset(MASBoxValue((__VA_ARGS__)))
#define offset(...)            mas_offset(__VA_ARGS__)
​
- (MASConstraint * (^)(NSValue *value))valueOffset {
  return ^id(NSValue *offset) {
    NSAssert([offset isKindOfClass:NSValue.class], @"expected an NSValue offset, got: %@", offset);
     [self setLayoutConstantWithValue:offset];
    return self;
   };
}
​
- (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);
   }
}
​
- (void)setOffset:(CGFloat)offset {
  self.layoutConstant = offset;
}

能够看出offset办法会依据传入的类型,去设置offset/centerOffset/sizeOffset/insets特点。而设置这些特点,最后都会承认对应束缚特点的layoutConstant

(6)MASViewConstraintmultipliedBy(...)办法

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

由于MASViewConstraint目标初始化时layoutMultiplier为1.0时,所以layoutMultiplier为1.0时常常不写。这儿也便是将layoutMultiplier特点赋值。

(7)总结

make.left.equalTo(superview.mas_left).with.offset(padding.left);

至此,这一整句代码的流程就能够总结为:

设置MASViewConstraint目标的以下特点

firstViewAttribute.item = view1
firstViewAttribute.layoutAttribute = NSLayoutAttributeLeft
layoutRelation = NSLayoutRelationEqual
secondViewAttribute.item = superview
secondViewAttribute.layoutAttribute = NSLayoutAttributeLeft
layoutMultiplier = 1.0
layoutConstant = padding.left

对比以下代码,有没有感觉和NSLayoutConstraint写束缚时的参数如出一辙

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

有了这些特点之后,Masonry自然就能够经过mas_makeConstraints/mas_updateConstraints/mas_remakeConstraints办法进行增加/更新/移除后增加相应束缚。

接下来咱们经过剖析mas_makeConstraints办法,了解Masonry怎么完结束缚

三、怎么完结束缚

咱们先来看mas_makeConstraints的完成

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    //translatesAutoresizingMaskIntoConstraints 默认情况下,视图上的主动调整掩码会产生彻底确定的束缚视图的方位。这答应主动布局体系盯梢视图的帧。布局是手动操控的(例如,经过-setFrame:)。当您选择经过增加自己的束缚来运用主动布局来定位视图时,您必须将此特点设置为NO
  self.translatesAutoresizingMaskIntoConstraints = NO;
  MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
  block(constraintMaker);
  return [constraintMaker install];
}

能够看到该办法首要创立了constraintMaker,然后调用block,也便是设置并增加了block中的每一条束缚到constraints中,然后回来[constraintMaker install],咱们就来研讨MASConstraintMaker的install办法

(1)MASConstraintMaker的install办法

- (NSArray *)install {
    //removeExisting在mas_remakeConstraints情况下为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) {
    constraint.updateExisting = self.updateExisting;
     [constraint install];
   }
   [self.constraints removeAllObjects];
  return constraints;
}

①首要,假如调用的是mas_remakeConstraints会先卸载之前的束缚

②将constraints中的每条束缚执行install,然后清空constraints。此时咱们应该检查MASConstraint的install办法

(2)MASConstraint的install办法

- (void)install {
  //...
  
  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];
   }
}

代码较多,这儿就简略总结一下。

① 假如secondViewAttribute =nil,且束缚特点不为width和height时,默认设置secondLayoutItem为view.superview、secondLayoutAttribute为firstViewAttribute.layoutAttribute。eg make.left.equalTo(@10)

②用对应的特点构造了MASLayoutConstraint束缚目标

③确定要设置安装束缚的installedView为 (1)最近的公共先人view (2)宽高特点束缚时,为firstViewAttribute.view (3)firstViewAttribute.view.superview

④判断是否是更新束缚,是则更新,不是则installedView增加该束缚 ([self.installedView addConstraint:layoutConstraint];)

由于MASLayoutConstraint继承自NSLayoutConstraint,所以这儿installedView (MAS_VIEW类型)的addConstraint办法,其实也便是UIView的addConstraint办法,也就成功地为installedView增加上了该束缚。

//Adds a constraint on the layout of the receiving view or its subviews.
- (void)addConstraint:(NSLayoutConstraint *)constraint API_AVAILABLE(ios(6.0)); // This method will be deprecated in a future release and should be avoided. Instead, set NSLayoutConstraint's active property to YES.

总结

(1)Masonry增加束缚流程总结

经过block中的每一行创立一个束缚MASViewConstraint,增加到束缚数组constraints中,经过install办法逐条增加到对应的installedView上。

(2)equalTo和mas_equalTo、offset和mas_offset功能根本相同,只不过mas_equalTomas_offset增加了对数字字面量的支持(即_MASBoxValue)。

     make.height.equalTo(123.0);   //error: Passing 'double' to parameter of incompatible type '__strong id'
     make.height.mas_equalTo(123.0);

(3)假如束缚特点不为宽高,且equalTo/mas_equalTo的目标是父view的相同特点,可省掉mas_equalTo。(即不写equalTo/mas_equalTo,默认相对父view布局)

   [self.view addSubview:view1];
   [view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    //由于view1.superview = self.view, 所以以下两句代码效果相同
    make.right.mas_equalTo(self.view.mas_right).mas_equalTo(-20);   
    make.right.mas_equalTo(-20);
   }];