iOS开发过程当中很大一部份内容就是界面布局和跳转,iOS的布局方式也经历了 显式坐标定位方式 --> autoresizingMask --> iOS 6.0推出的自动布局(Auto Layout)的逐步优化,至于为何推出自动布局,确定是由于以前的方法很差用(哈哈 简直是废话),具体如何很差用以及怎么变化你们能够瞅瞅 这篇文章。iOS6.0推出的自动布局实际上用布局约束(Layout Constraint)来实现,经过布局约束(Layout Constraint)能够肯定两个视图之间精确的位置的相对距离,为此,iOS6.0推出了NSLayoutConstraint来定义约束,使用方法以下:html
[NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeRight multiplier:1 constant:10]; //翻译过来就是:view1的左侧,在,view2的右侧,再多10个点,的地方。
布局约束的添加规则:ios
(1)对于两个同层级 view 之间的约束关系,添加到它们的父 view 上
数组
(2)对于两个不一样层级 view 之间的约束关系,添加到他们最近的共同父 view 上
(3)对于有层次关系的两个 view 之间的约束关系,添加到层次较高的父 view 上
(4)对于好比长宽之类的,只做用在该 view 本身身上的话,添加到该 view 本身上
具体关于NSLayoutConstraint的详细使用方法参见:NSLayoutConstraint-代码实现自动布局。今天咱们文章的主角——Masonry框架其实是在NSLayoutConstraint的基础上进行封装的,这一点在后面的源码分析中咱们详细解释。架构
当我们须要对控件的top,bottom,left,right进行约束就特别麻烦,在OC中有一个库Masonry
对NSLayoutConstraint
进行了封装,简化了约添加约束的方式和流程。用Masonry框架进行布局很是简单,主要特色是采用链式语法进行布局,这一点使得咱们在使用和代码布局上更为方便,利用Masonry进行布局的前提条件之一是 布局视图必须先被添加到父视图中。简单示例以下代码,关于Masonry框架的使用并非本文的重点,详情能够参见:Masonry介绍与使用实践:快速上手Autolayout。若是你的项目是Swift语言的,那么就得使用SnapKit布局框架了,SnapKit其实就是Masonry的Swift版本,二者虽然实现语言不一样,可是实现思路大致一致。框架
UIView *sv1 = [UIView new]; //利用Masonry进行布局的前提条件之一是 布局视图必须先被添加到父视图中 [sv addSubview:sv1]; [sv1 mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10)); /* 等价于 make.top.equalTo(sv).with.offset(10); make.left.equalTo(sv).with.offset(10); make.bottom.equalTo(sv).with.offset(-10); make.right.equalTo(sv).with.offset(-10); */ /* 也等价于 make.top.left.bottom.and.right.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10)); */ }];
Masonry框架是在NSLayoutConstraint的基础上进行封装的,其涉及到的内容也是很是繁多。在进行源码剖析时咱们从咱们常常用到的部分出发,一层一层进行解析和研究。ide
首先,咱们先大致了解一下调用 mas_makeConstraints 进行布局时的流程步骤,其实另外两个 mas_updateConstraints 和 mas_remakeConstraints 的流程也基本上是同样的。函数
MASContraintMaker
的实例对象make)@implementation MAS_VIEW (MASAdditions) - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block { //关闭AutoresizingMask的布局方法是咱们进行Auto Layout布局的前提步骤 self.translatesAutoresizingMaskIntoConstraints = NO; //建立一个约束建立器 MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; //在block中配置constraintMaker对象,即将constraintMaker传入block中(其实就是咱们在block中用来添加约束的make)进行约束配置 block(constraintMaker); //约束安装并以数组形式返回 return [constraintMaker install]; } ...
约束安装方法 [constraintMaker install]; 的源代码以下,这部分的代码很简单,主要就是对当前约束建立器中的约束进行更新,由于除了咱们这个 mas_makeConstraints 方法中会调用该方法以外, mas_updateConstraints 和 mas_remakeConstraints 中都会调用该方法进行约束的安装,因此在该约束安装方法中考虑了约束的删除和是否有更新等状况的处理。工具
//install方法主要就是对下面这个约束数组进行维护 @property (nonatomic, strong) NSMutableArray *constraints; - (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; }
- (void)install { if (self.hasBeenInstalled) { return; } ... //MASLayoutConstraint其实就是在NSLayoutConstraint基础上添加了一个属性而已 //@interface MASLayoutConstraint : NSLayoutConstraint 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 (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]; } }
经过上面的分析和研究,咱们基本上已经把Masonry框架中主要布局方法的主流程了解清楚了。由于这是第一次学习iOS第三方框架的源码,在这个学习过程当中也走了不少弯路,最开始是从最基本的类开始看,后来发现越看越不懂,不知道这个属性的定义在何时用到,是什么含义((ノへ ̄、)捂脸。。。)。后来经过摸索才知道源码学习应该直接从用到的方法着手,而后一步一步深刻分析源码中每一步的目的和意义,顺藤摸瓜,逐个击破。源码分析
下面的代码是比较经常使用的几种Masonry的布局格式,咱们能够看到都是经过点语法的链式调用进行布局的。以前在学习Java和Android的过程当中接触过链式语法,在Java中要实现这种链式语法很简单,无非就是每一个方法的返回值就是其自己,由于Java的方法调用是经过点语法调用的,因此很容易实现。可是在OC中,方法调用都是经过 [clazz method:parm]; 的形式进行调用的,那么Masonry框架中是怎么实现的呢?布局
make.top.equalTo(sv).with.offset(10); make.left.right.mas_equalTo(sv).mas_offset(0.0f); make.top.left.bottom.and.right.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10));
一样的学习方法,咱们来看一下源码中各个属性或方法是怎么实现的,最重要的缘由就是getter方法和Objective-C 里面,调用方法是可使用点语法的,但这仅限于没有参数的方法。
//MASConstraintMaker.h文件 @interface MASConstraintMaker : NSObject @property (nonatomic, strong, readonly) MASConstraint *left; @property (nonatomic, strong, readonly) MASConstraint *top; @property (nonatomic, strong, readonly) MASConstraint *right; @property (nonatomic, strong, readonly) MASConstraint *bottom; @property (nonatomic, strong, readonly) MASConstraint *leading; @property (nonatomic, strong, readonly) MASConstraint *trailing; @property (nonatomic, strong, readonly) MASConstraint *width; @property (nonatomic, strong, readonly) MASConstraint *height; @property (nonatomic, strong, readonly) MASConstraint *centerX; @property (nonatomic, strong, readonly) MASConstraint *centerY; @property (nonatomic, strong, readonly) MASConstraint *baseline; #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100) @property (nonatomic, strong, readonly) MASConstraint *firstBaseline; @property (nonatomic, strong, readonly) MASConstraint *lastBaseline; #endif #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) @property (nonatomic, strong, readonly) MASConstraint *leftMargin; @property (nonatomic, strong, readonly) MASConstraint *rightMargin; @property (nonatomic, strong, readonly) MASConstraint *topMargin; @property (nonatomic, strong, readonly) MASConstraint *bottomMargin; @property (nonatomic, strong, readonly) MASConstraint *leadingMargin; @property (nonatomic, strong, readonly) MASConstraint *trailingMargin; @property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins; @property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins; #endif @property (nonatomic, strong, readonly) MASConstraint *edges; @property (nonatomic, strong, readonly) MASConstraint *size; @property (nonatomic, strong, readonly) MASConstraint *center; ... @end //MASConstraintMaker.m文件 @implementation MASConstraintMaker //每一个方法返回的也是MASConstraint对象,其实是MASViewConstraint、MASCompositeConstraint类型的对象,见最后的函数中标红的注释 - (MASConstraint *)top { //将对应的系统自带的约束布局的属性NSLayoutAttributeTop传入 return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop]; } //过渡方法 - (MASConstraint *)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]; ... //添加约束 if (!constraint) { //设置约束的代理是self newConstraint.delegate = self; [self.constraints addObject:newConstraint]; } //返回MASViewConstraint类型的对象 return newConstraint; } ... @end
@interface MASViewConstraint : MASConstraint <NSCopying>
//MASConstraint.h文件 @interface MASConstraint : NSObject /** * Creates a new MASCompositeConstraint with the called attribute and reciever */ - (MASConstraint *)left; - (MASConstraint *)top; - (MASConstraint *)right; - (MASConstraint *)bottom; - (MASConstraint *)leading; - (MASConstraint *)trailing; - (MASConstraint *)width; - (MASConstraint *)height; - (MASConstraint *)centerX; - (MASConstraint *)centerY; - (MASConstraint *)baseline; #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100) - (MASConstraint *)firstBaseline; - (MASConstraint *)lastBaseline; #endif #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) - (MASConstraint *)leftMargin; - (MASConstraint *)rightMargin; - (MASConstraint *)topMargin; - (MASConstraint *)bottomMargin; - (MASConstraint *)leadingMargin; - (MASConstraint *)trailingMargin; - (MASConstraint *)centerXWithinMargins; - (MASConstraint *)centerYWithinMargins; #endif ... @end
//MASConstraint.m文件 - (MASConstraint *)top { //这里会调用MASViewConstraint中的addConstraintWithLayoutAttribute:方法 return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop]; } //MASViewConstraint.m文件 - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation"); //调用代理的方法,以前咱们说过设置的代理是MASConstraintMaker对象make,因此调用的其实是MASConstraintMaker添加约束的方法,这就是咱们再上面第一步讲到的方法 return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute]; } //MASConstraintMaker.m文件 - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute]; MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute]; //当传入的constraint不为空时,即此调用不是第一个,make.toip.left在left时的调用 if ([constraint isKindOfClass:MASViewConstraint.class]) { //则用MASCompositeConstraint做为返回值,即组约束 NSArray *children = @[constraint, newConstraint]; MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children]; //设置代理 compositeConstraint.delegate = self; //从新设置 [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint]; //返回 MASCompositeConstraint对象return compositeConstraint; } if (!constraint) { //设置代理 newConstraint.delegate = self; //设置约束 [self.constraints addObject:newConstraint]; } return newConstraint; }
在上一小节咱们提到了链式语法的主要缘由在于在Objective-C 里面,调用方法是可使用点语法的,但这仅限于没有参数的方法,可是相似mas_equalTo、mas_offset等带参数传递的方法依旧能够用链式语法又是怎么一回事呢?最关键的一环就是 block。block就是一个代码块,可是它的神奇之处在于在内联(inline)执行的时候还能够传递参数。同时block自己也能够被做为参数在方法和函数间传递。block做为参数传递很常见,就是在咱们的Masonry框架中添加约束的方法 - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block 中就是讲一个block做为参数进行传递的。
一样在MASConstraint中,咱们能够看到mas_equalTo、mas_offset等带参方法的定义以下,咱们能够看到,方法的定义中并无参数,可是返回值是一个带参的block,而且该block还返回一个MASConstraint对象(MASViewConstraint或者MASCompositeConstraint对象),因此方法的定义和使用都没有什么问题,和上一小节分析的内容差很少。最主要的区别就是这里返回值为带参数的block,而且该block的参数能够经过咱们的方法进行传值。关于带参block做为返回值得用法能够参见 此连接的文章。
- (MASConstraint * (^)(MASEdgeInsets insets))insets; - (MASConstraint * (^)(CGFloat inset))inset; - (MASConstraint * (^)(CGSize offset))sizeOffset; - (MASConstraint * (^)(CGPoint offset))centerOffset; - (MASConstraint * (^)(CGFloat offset))offset; - (MASConstraint * (^)(NSValue *value))valueOffset; - (MASConstraint * (^)(CGFloat multiplier))multipliedBy; - (MASConstraint * (^)(CGFloat divider))dividedBy; - (MASConstraint * (^)(MASLayoutPriority priority))priority; - (MASConstraint * (^)(void))priorityLow; - (MASConstraint * (^)(void))priorityMedium; - (MASConstraint * (^)(void))priorityHigh; - (MASConstraint * (^)(id attr))equalTo; - (MASConstraint * (^)(id attr))greaterThanOrEqualTo;
//MASConstraint.m文件 - (MASConstraint * (^)(id))mas_equalTo { return ^id(id attribute) { //多态调用子类MASViewConstraint或者MASCompositeConstraint的对应方法 return self.equalToWithRelation(attribute, NSLayoutRelationEqual); }; } //MASViewConstraint.m中对应的方法,MASCompositeConstraint其实也相似,只是循环调用每个子约束的该方法 - (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { return ^id(id attribute, NSLayoutRelation relation) { //若是传入的参数是一个数组 则逐个约束解析后以组形式添加约束 if ([attribute isKindOfClass:NSArray.class]) { NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation"); NSMutableArray *children = NSMutableArray.new; for (id attr in attribute) { MASViewConstraint *viewConstraint = [self copy]; viewConstraint.layoutRelation = relation; viewConstraint.secondViewAttribute = attr; [children addObject:viewConstraint]; } MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children]; compositeConstraint.delegate = self.delegate; [self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint]; return compositeConstraint; } else { //单一约束 则直接赋值 NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation"); self.layoutRelation = relation; self.secondViewAttribute = attribute; return self; } }; }
进过上面的原理分析,大致理解了其调用和实现眼里,接下来,咱们经过下面的这句代码在容器中的演变过程来进行总体感觉一下,下面的演变过程来自:Masonry源码学习,原文有几处有点小问题修改过,你们参考的时候注意甄别和判断。
make.top.right.bottom.left.equalTo(superview)
在上面的过程当中能够看到:
盗用iOS开发之Masonry框架源码解析中的一张图,这张图将Masonry框架的架构阐述的很清晰,Masonry框架主要分为4个部分:
MASConstraintMaker
类就是一个工厂类,负责建立和安装MASConstraint
类型的对象(依赖于MASConstraint
接口,而不依赖于具体实现)。