美文网首页
Masonry源码解析

Masonry源码解析

作者: LNG61 | 来源:发表于2019-05-05 14:03 被阅读0次

    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源码解析

          本文链接:https://www.haomeiwen.com/subject/vsuouqtx.html