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回调的原因,还是要花点时间才能读懂。

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

推荐阅读更多精彩内容

  • Masonry 是一个轻量级的用于自动布局(AutoLayout)的第三方框架,以其简洁的使用方式,受到广大开发者...
    ifelseboyxx阅读 3,074评论 3 46
  • Masonry一直是OC中优秀的Auto Layout框架,尤其是其优雅的点链式语法设计,为人津津乐道。 今天我们...
    iDeveloper阅读 961评论 1 8
  • Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使...
    丘比沙拉阅读 3,178评论 2 19
  • Masonry 源码解析 上图是 Masonry 的大体结构。 Masonry 主要分两层,一部分通过主要的用户接...
    sim_cai阅读 503评论 0 1
  • Masonry这个框架是使用代码进行自动布局使用的,它的使用非常广泛,这段时间一直在学习这个框架,因此想把学到的东...
    雪山飞狐_91ae阅读 2,176评论 3 24