Masonry源代码分析

使用Autolayout也有一段时间了,auto layout的基本概念非常简单,都是围绕约束进行的,API更是只有两个,但是使用起来感觉很麻烦。最近看到我们这边其他部门的应用使用了很多Masonry来处理UI,看起来非常清爽,链式调用看起来非常容易阅读,使用起来非常方便。但是这种之前ASI给的教训非常深刻,尤其这种大规模基础性地使用第三方开源库,需要确保可控才敢用,至少可以读懂代码并且能够局部优化代码,这是我认为的可控。尤其是这种非常基础的类库,会分布在各个模块之中,一旦出现不兼容,几乎是无法重写的。于是花了一两天时间,把这个代码研究了一下,发现比我想象中要好一些,代码设计地也非常简洁巧妙。

一. 源文件说明

1. MASConstraint: 这个是虚类是用来实现链式调用的父类。MASConstraint子类可以用来表示单独的一个NSLayoutConstraint约束(MASViewConstraint)或者一组NSLayoutConstraint约束(MASCompositeConstraint)。

2. MASConstraint+Private.h: 用于隐藏MASConstraint的私有方法,这些私有方法不会被外部调用者获取,但是类库内部却可以得到,这是个很好的设计模式,在继承中相当于protected。通过定义了较为重要的MASConstraintDelegate代理。

3. MASCompositeConstraint: 这个类用于表示一组NSLayoutConstraint约束,内部包含一个MASViewConstraint的数组做为childs。当调用这个类的类似equalTo或者install等方法的时候,这个类就会调用它的childs所对应的方法。这个类相当于一个MASViewConstraint容器,用于可以方便进行多个属性的操作,可以大大减少工作量。

4. MASViewConstraint:这个是Masonry DSL语法的核心解析类,用来表示对应NSLayoutConstraint,并将表示的属性在install的时候解析为对应的NSLayoutConstraint类,并加入到对应View中。

5. MASConstraintMaker: 这个是整个DSL过程的控制中心,控制整个添加过程。MASViewConstraint和NSCompositeConstraint都在这个maker中生成,maker并且会管理这些constraint的引用,在合适的时候,将这些constraint解析出来。重要的核心方法是实现了 MASConstraintDelegate 中的这些方法,这几个方法中是生成constraint的核心方法。注意,NSViewConstraint类如果没有加入到NSCompositeConstraint中,它的MASConstraintDelegate是maker;如果它是NSCompositeConstraint的child,则它的delegate是MASCompositeConstraint,但是最终还是会之中maker中的delegate方法。而MASCompositeConstraint的MASConstraintDelegate是maker。注意这个类提供的语法糖,用于方便地进行一组约束的操作,例如edges/size/center等属性。

6. View+MASAdditions:使用mas_makeConstraints/mas_updateConstraints/mas_remakeConstraints等核心入口方法来设置约束,最常用的的核心是mas_updateConstraints,一般来说,使用这个方法即可。

7. MASViewAttribute:用来封装UIView和它的NSLayoutAttribute属性,简单的hold这些引用。

8. MASLayoutConstraint:简单继承NSLayoutConstraint

9. MASUtilities.h:这个文件有个点需要注意,这里定义了MASBoxValue宏,用来将类似int/CGRect/CGSize等值使用NSValue进行封装,变为NSObject对象,可以使MASConstraint的类似equalTo等函数有一个便捷函数mas_equalTo可以使用,例如maker.left.equalTo(@(100))可以写为 maker.left.mas_equalTo(100)。mas_equalTo实际上是#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))宏。

10. NSArray+MASAdditions:一些辅助方法。

二. 代码阅读

通过下面的代码的执行过程说明一下masorny的执行过程
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. [testView mas_updateConstraints:^(MASConstraintMaker *make) {  
  2.      make.top.left.width.height.equalTo(@(100)).priorityHigh();  
  3. }];  

先说block中的内容执行:
1. make.top:
MASConstraintMaker的top方法 -> addConstraintWithLayoutAttribute:方法 -> [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute] ,可以发现最终调用的是MASConstraintMaker的MASConstraintDelegate的constraint:addConstraintWithLayoutAttribute:方法,在这个方法的实现内,生成了MASViewConstraint对象作为返回值。可以说这个方法是链式调用的核心方法,由于constraint为nil,所以这里执行的代码很简单。注意这里的把newConstarint的MASConstraintDelegate设置为self,也就是说新生成的MASViewConstraint的delegate是MASViewConstraint。所以make.top的返回值是MASViewConstraint类。
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {  
  2.     MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];  
  3.     MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];  
  4.       
  5.     ….  
  6.   
  7.     if (!constraint) {  
  8.         newConstraint.delegate = self;  
  9.         [self.constraints addObject:newConstraint];  
  10.     }  
  11.     return newConstraint;  
  12. }  

2. make.top.left:
这里的left执行的是MASViewConstraint的left方法,接着调用MASViewConstraint的addConstraintWithLayoutAttribute:方法:
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (MASConstraint *)left {  
  2.     return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];  
  3. }  

注意,这里的返回值,不是self,而是addConstraintWithLayoutAttribute方法的返回值。而MASViewConstraint的这个方法的实现如下:
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {  
  2.     NSAssert(!self.hasLayoutRelation@"Attributes should be chained before defining the constraint relation");  
  3.   
  4.     return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];  
  5. }  

之前我们知道这个MASViewConstraint的delegate就是MASConstraintMaker,所以会再次执行
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {  
  2.     NSUInteger index = [self.constraints indexOfObject:constraint];  
  3.     NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);  
  4.     [self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];  
  5. }  
  6.   
  7. - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {  
  8.     MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];  
  9.     MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];  
  10.     if ([constraint isKindOfClass:MASViewConstraint.class]) {  
  11.         //replace with composite constraint  
  12.         NSArray *children = @[constraint, newConstraint];  
  13.         MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];  
  14.         compositeConstraint.delegate = self;  
  15.         [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];  
  16.         return compositeConstraint;  
  17.     }  
  18.     if (!constraint) {  
  19.         newConstraint.delegate = self;  
  20.         [self.constraints addObject:newConstraint];  
  21.     }  
  22.     return newConstraint;  
  23. }  

由于constraint参数为MASViewConstraint类,这里我们可以看出来,最终的返回值不再是MASViewConstraint,而变成了MASCompositeConstraint类,这个类不仅仅包含left约束(MASViewConstraint),同时也会把top约束(MASViewConstraint)也加入进去,同时会把maker中的constraints数组中top约束(MASViewConstraint)删掉,替换为刚刚生成的MASCompositeConstraint对象。注意,在MASCompositeConstraint对象的initWithChildren方法中,会把所有的child的delegate设置为MASCompositeConstraint对象,而MASCompositeConstraint的delegate则会变成maker。
所以,我们可以看出,make.top.left的返回值是一个MASCompositeConstraint对象,里面包含了top和left约束对象(MASViewConstraint)。

3. make.top.left.width:
这里的width是执行MASCompositeConstraint对象的width方法,接着调用MASCompositeConstraint的addConstraintWithLayoutAttribute:方法
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {  
  2.     [self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];  
  3.     return self;  
  4. }  

这里一定要注意,跟MASViewConstraint和MASConstraintMaker的addConstriantWithLayoutAttribute方法不一样,这里的返回值,不是新生成的MASConstraint对象,而是self,也就是还是这个MASCompositeConstraint对象。这个类调用的是MASCompositeConstraint的方法:
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {  
  2.     id<MASConstraintDelegate> strongDelegate = self.delegate;  
  3.     MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];  
  4.     newConstraint.delegate = self;  
  5.     [self.childConstraints addObject:newConstraint];  
  6.     return newConstraint;  
  7. }  

在这个方法中,最终还是会调用delegate的方法,也就是MASConstraintMaker的方法,上面我们可以看到在MASConstraintMaker的这个方法中,只执行了一部分代码:
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {  
  2.     MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];  
  3.     MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];  
  4.   
  5.     ...  
  6.       
  7.     return newConstraint;  
  8. }  

所以在maker中仅仅是生成MASViewConstraint而已,最终这个对象会被加入到MASCompositeConstraint的child中,并且这个MASViewConstraint的delegate会被设置为MASCompositeConstraint。
所以make.top.left.width执行返回的仍然是make.top.left时生成的MASCompositeConstraint对象,只是会为width生成一个MASViewConstraint约束,并且加入到MASCompositeConstraint对象的child中,并不会把这个约束加入到maker的constraints数组中。

至于,make.top.left.width.height是跟这个一样的执行步骤,不再赘诉。

4. 至于equalTo的调用过程,是使用OC的Dot notation语法,使用这个语法可以直接调用无参数的方法。然后直接使用返回的block进行调用,实现了对约束的设置。

5. mas_updateConstraints方法中,上述block中的内容执行完之后,会调用maker的install方法,这个方法中,会遍历maker中constraints数组,然后调用MASConstraint的install方法。而MASCompositeConstraint对象的install方法会直接调用其child的install方法,并不会额外进行其他操作。所以最终的核心操作是MASViewConstraint的install方法,这个方法中,会生成NSLayoutConstraint约束,然后添加到对应的view中。这个地方其实比较简单,看代码即可。

三. 注意点

1. 通过链式调用可以设置多个属性,比如maker.left.right.top等,这些方法会返回一个MASCompositeConstraint对象,代表left/right/top这些约束的所有MASViewConstraint对象都会被添加到MASCompositeConstraint之中,通过这种方式实现链式调用,可以大大减少代码量。同时通过maker提供的center/edges/size函数也返回MASCompositeConstraint对象,里面同时包含了MASViewConstraint对象,也可以实现同时设置多个属性。注意MASCompositeConstraint源文件中的这段代码,注意这里返回的不是新生成的MASViewConstraint对象:
[objc]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {  
  2.      [self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];  
  3.      return self;  
  4. }  


四. 代码阅读心得

1. 使用NSException来解决OC中没有虚函数语法的问题,如果子类未实现虚方法,则直接抛出异常;在iOS中,我们对异常的使用远远不足,思路太过局限于C的思路,比如说出错之后,往往使用返回-1这种方式解决,这点在处理一些比较突出的异常时就非常不够用。例如,在JSON解析的时候对应的数据不对的问题,这个问题再跟服务器约定之后,一般不会出问题,但是一旦遇到问题就对客户端的用于体验造成非常不好的影响,现在的思路是可以使用@try{}@catch来解决,后面会在这方面做一些尝试。

2. 大量使用NSAssert断言工具来调试代码,我自己这方面之前更加喜欢使用if来避免进行各种容错,虽然这种方式,可以避免crash等严重问题,但是开发过程中,会降低开发效率。

3. 对block的灵活使用,这部分也是我最近思考比较多的地方,之前我对block的使用也觉得较多,但是现在看来,使用深度和灵活度都不够,一方面用来替代delegate做回调,这种方式非常灵活便捷,并且逻辑清晰。但是看masonry中的时候,才发现使用block做返回做参数,可以把写代码的过程变得简洁很多,在masonry中,equalTo这些方法的实现就是最好的例子。

4. 类似MASConstraint+Private.h的方式使用category实现类似protected的内部方法继承,这个也比较有意思,也是我之前一个挺困扰的问题,在写类库的时候,可以避免公开接口混乱以及过大的问题,从而很好地划分公开以及内部方法。

5. 类库的封装,调用时要足够简单便捷,通过隐藏大量的内部实现细节,只暴露必要的部分,这样第三方使用者使用起来才不会产生困惑。这部分我自己做的不足,很多时候写代码和类库的时候,写的过于冗余和啰嗦,这也可能是OC没有命名空间带来的习惯影响,后续转向swift之后肯定会大大改善这些问题。

6. Dot Notation语法的使用,也是masonry可以实现链式调用的基础语法,使用dot notation调用方法( 无参数方法)。结合block做返回变量来进行使用,就可以把代码变得非常简单,这种方式就实现所谓的函数式编程。注意,为了更好地维护代码规范,dot notation调用无参数方法这个语法要慎用,一般来说,方法调用就要使用”[]"语法,只有变量使用 “.”,可以避免混乱,增加代码的可阅读性。参考: http://stackoverflow.com/questions/2375943/objective-c-dot-notation-with-class-methodshttp://underscorem.org,还有就是参考MASViewConstraint的equalTo等方法的实现代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值