Masonry源码解析

Masonry简介

Masonry是用于自动布局的第三方框架,对苹果的自动布局框架进行了一层封装,其接口比起官方的接口来,显得非常简洁。

常用接口

常用接口主要有以下三个:

@interface MAS_VIEW(MASAddtions)
// make constraint
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make)block;
// update constraint
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make)block;
// remake constraint
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make)block;
@end

mas_makeConstraints接口

mas_makeConstraints接口是用来创建约束的,该方法实现如下:

- (NSArray *)mas_makeConstraints:(void(^)(MSAConstraintMaker *make)block{
  self.translatesAutoresizingMaskInfoConstraints = NO; 
  MSAConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
  block(constraintMaker);
  return [constraintMaker install];
}

该方法主要做了以下几件事:

  • 设置属性translatesAutoresizingMaskInfoConstraintsNO,该属性代表用来开启自动布局;
  • 创建MASConstraintMaker的实例constraintMaker,并传给block
  • 调用[constraintMaker install]

MSAConstraintMaker

MSAConstraintMaker封装了创建约束的工厂方法,并最终调用install将约束‘安装’起来。block(constraintMaker)是用于配置constraintMaker的相关属性,假设有以下代码make.left.mas_equal(0);,那是对constraintMaker.left属性进行配置,我们来看下该代码在MSAConstraintMaker.m中的实现。

@property(nonatomic, strong, readonly) MASConstraint *left;

- (MASConstraint *)left{
  return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MSAConstraint *)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];
  newConstraint.delegate = self;
  [self.constraint addObject:newConstraint];
  return newConstraint;
}

从上面可以看出,通过make.left最终调用了constraint:addConstraintWithLayoutAttribute函数,生成了一个MASViewConstraint的实例,之后left.mas_equal(0)则是对MASViewConstraint的操作。
在介绍MASViewConstraint之前,我们先看下[constraintMaker 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;
}

install其实是遍历self.constraints,对所有MASConstraint进行install

另外两个常用的更新和重新设置约束方法:

/// 更新约束
- (NSArray *)mas_updateConstraints:(void(^)(MSAConstraintMaker *make)block{
  self.translatesAutoresizingMaskInfoConstraints = NO; 
  MSAConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
  constraintMaker.updateExisting = YES; // 标记位更新已存在的
  block(constraintMaker);
  return [constraintMaker install];
}

/// 重新设置约束
- (NSArray *)mas_remakeConstraints:(void(^)(MSAConstraintMaker *make)block{
  self.translatesAutoresizingMaskInfoConstraints = NO; 
  MSAConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
  constraintMaker.removeExisting = YES; // 标记为需要移除之前的
  block(constraintMaker);
  return [constraintMaker install];
}

MASConstraint

MASConstraint提供链式语法来创建约束,比如.mas_equal(view).mas_equal(0);这种形式,用来设置一些约束的属性,其实现如下:

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

- (MASConstraint *)width{
  return self;
}

MASViewAttribute

MASViewAttribute用于存储view和对应的NSLayoutAttribute

MASViewConstraint

之前提到MSAConstraintMaker会创建MASViewConstraint,这个类继承自MASConstraint,代表单个constraint。在mas_makeConstraint中最后一个步骤是调用[constraintMaker install],而这个方法正是对MSAConstraintMaker中的每个MASViewConstraint依次调用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;
  
  if(!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute){
    secondLayoutItem = firstLayoutItem.superview;
    secondLayoutAttribute = firstLayoutAttribute;
  }

  MALayoutConstraint *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)
    self.installedView = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
  else if(self.firstViewAttribute.isSizeAttribute)
    self.installedView = self.firstViewAttribute.view;
  else
    self.installedView = self.firstViewAttribute.superview;

  MASLayoutConstraint *existingConstraint = nil;
  if(self.updateExisting){
    existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
  }
  if(existingConstraint){
    existingConstraint.constant = layoutConstraint.constant;
    self.layoutConstraint = existingConstraint;
  }else{
    [self.installedView addConstraint:layoutConstraint];
    self.layoutConstraint = layoutConstraint;
    [firstLayoutItem.mas_installedConstraints addObject:self];
  }
}

该方法主要流程如下:

  • 如果hasBeenInstalled已经标记过了,则代表该约束已经添加过了,直接return
  • 如果支持NSLayoutConstraint.active(iOS8.0+)并且self.layoutConstraint已存在,则直接设置为active即可。
  • 获取约束的firstItemsecondItem,如果secondItem不存在,将seconItem=firstItem.superview,默认跟父view创建约束关系;
  • 生成约束,[MASLayoutConstraint constraintWithItem:firstLayoutItem attribute:firstLayoutAttribute relatedBy:self.layoutRelation toItem:secondLayoutItem attribute:secondLayoutAttribute multiplier:self.layoutMultiplier constant:self.layoutConstant]
  • 让约束生效,将生成的约束加到对应的view上。

对应的方法是uninstall,将约束移除,其实现如下:

- (void)uninstall{
  if([self supportActiveProperty]){
    self.layoutConstraint.active = NO;
    [self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
    return;
  }

  [self.installedView removeConstraint:self.layoutConstraint];
  self.layoutConstraint = nil;
  self.installedView = nil;
  [self.firstViewAttribute.view.mas_installedConstraints removeObject:self];
}

MASCompositeConstraint

MASCompositeConstraintMASViewConstraint的集合。

实例

接下来分析一个例子,看看是如何创建约束的:

[self mas_makeConstraints:^(MASConstraintMaker *maker){
  make.left.mas_equal(10).priorityLow();
}];

通过之前的分析,我们将上面代码展开,得到以下代码:

self.translatesAutoresizingMaskInfoConstraints = NO; 
MSAConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.left.mas_equal(10).priorityLow();
[constraintMaker install];

constraintMaker.left.mas_equal(10).priorityLow()

  • 首先是constraintMaker.left,返回了一个MASConstraint对象,展开是这样的:
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeft];
MASViewConstraint *masConstraint= [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
masConstraint.mas_equal(10).priorityLow();
  • mas_equal其实是一个宏,定义如下:
#define mas_equalTo(...) equal(MASBoxValue((__VA_ARGS)))
#define MASBoxValue(value) _MASBoxValue(@encode(_typeof__((value))), (value))

_MASBoxValue是通过对象的encode类型转换成对应的NSNumberNSValue
这样的话代码就变成这样了:

MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self layoutAttribute:NSLayoutAttributeLeft];
MASViewConstraint *masConstraint= [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
id value = _MASBoxValue(@encode(__typeof__(10), 10);
masConstraint.equal(value).priorityLow();
  • equalpriorityLow其实是设置属性的,展开如下:
masConstraint.equal(@10);
  -> masConstraint.equalWithRelation(@0, NSLayoutRelationEqual);
  -> masConstraint.layoutRelation = NSLayoutRelationEqual;
       [masConstraint setSecondViewAttribute:@10]
  -> masConstraint.offset = @10; // 由于这里是NSNumber,就设置为offset了,如果是UIView,则初始化为secondViewAttribute

masConstraint.priorityLow();
  -> masConstraint.layoutPriority = MASLayoutPriorityDefalutLow;
  • 此时masConstraint的属性是这样的:
firstViewAttribute.item                       -> self
firstViewAttribute.layoutAttribute            -> NSLayoutAttributeLeft
secondViewAttribute.item                      -> self.superview
secondViewAttribute.layoutAttribute           -> NSLayoutAttributeLeft
layoutRelation                                -> NSLayoutRelationEqual
layoutMultiplier                              -> 1.0
layoutConstant                                -> 10
layoutPriority                                -> MASLayoutPriorityDefalutLow
  • 调用install方法创建约束:
MALayoutConstraint *layoutConstraint = [MASLayoutConstraint constraintWithItem:self attribute:firstLayoutAttribute relatedBy:NSLayoutAttributeLeft toItem:self.superview attribute:NSLayoutAttributeLeft multiplier:1 constant:10];
layoutConstraint.priority = MASLayoutPriorityDefalutLow;
  • 这样约束就成功创建完成了。

小结

Masonry对外提供的接口非常易用,但其实现因为大量用了block回调的原因,还是要花点时间才能读懂。

你可能感兴趣的:(Masonry源码解析)