转载

Masonry1.0.2 源码解析【ios】

Masonry1.0.2 源码解析

在了解Masonry框架之前,有必要先了解一下自动布局的概念。在iOS6之前,UI布局的方式是通过frame属性和Autoresizing来完成的,而在iOS6之后,苹果公司推出了AutoLayout的布局方式,它是一种基于约束性的、描述性的布局系统,尤其是苹果的手机屏幕尺寸变多之后,AutoLayout的应用也越来越广泛。

但是,手写AutoLayout布局代码是十分繁琐的工作(不熟悉的话,可以找资料体验一下,保证让你爽到想哭,^_^);鉴于此,苹果又开发了VFL的布局方式,虽然简化了许多,但是依然需要手写很多代码;如果,你希望不要手写代码,那么可以用xib来布局UI,可以图形化添加约束,只是xib的方式不太适合多人协作开发。综合以上的各种问题,Masonry出现了,这是一款轻量级的布局框架,采用闭包、链式编程的技术,通过封装系统的NSLayoutConstraints,最大程度地简化了UI布局工作。

本文主要分析一下Masonry的源码结构、布局方式和实现原理等等。

框架结构

Masonry框架的源码其实并不复杂,利用自己的描述语言,采用优雅的链式语法,使得自动布局方法简洁明了,并且同时支持iOS和MacOS两个系统。Masonry框架的核心就是MASConstraintMaker类,它是一个工厂类,根据约束的类型会创建不同的约束对象;单个约束会创建MASViewConstraint对象,而多个约束则会创建MAXCompositeConstraint对象,然后再把约束统一添加到视图上面。

Masonry1.0.2 源码解析【ios】

布局方式

Masonry的布局方式比较灵活,有mas_makeConstraints(创建布局)、mas_updateConstraints(更新布局)、mas_remakeConstraints(重新创建布局)三种:

1.mas_makeConstraints:

给视图(视图本身或父视图)添加新的约束

// 给view1添加约束,frame和superView一样 [view1 mas_makeConstraints:^(MASConstraintMaker *make) {     make.edges.equalTo(superview); }];

2.mas_updateConstraints:

更新视图的约束,从视图中查找相同的约束,如果找到,就更新,会设置maker的updateExisting为YES

// 更新view1的上边距离superView为60,宽度为100 [view1 mas_updateConstraints:^(MASConstraintMaker *make) {     make.top.equalTo(@60);     make.width.equalTo(@100); }];

3.mas_remakeConstraints:

给视图添加约束,如果视图之前已经添加了约束,则会删除之前的约束,会设置maker的removeExisting为YES

// 重新设置view1的约束为:顶部距离父视图为300,左边距离父视图为100,宽为100,高为50 [view1 mas_remakeConstraints:^(MASConstraintMaker *make) {     make.top.equalTo(superview).offset(300);     make.left.equalTo(superview).offset(100);     make.width.equalTo(@100);     make.height.equalTo(@50); }];

Masonry的布局相对关系也有三种:.equalTo(==)、.lessThanOrEqualTo(<=)、.greaterThanOrEqualTo(>=)。

Masonry的布局关系的参数也有三种:

1. @100 --> 表示指定具体值

2. view --> 表示参考视图的相同约束属性,如view1的left参考view2的left等

3. view.mas_left --> 表示参考视图的特定约束属性,如view1的left参考view2的right等

实现原理

Masonry是利用闭包和链式编程的技术实现简化操作的,所以需要对闭包和链式编程有一定的基础。下面会根据案例来具体分析一下Masonry的实现细节,代码实现的功能是设置view1的frame是CGRectMake(100, 100, 100, 100);其中,mas_equalTo(...)是宏,会被替换成equalTo(MASBoxValue((...))),功能是把基本类型包装成对象类型:

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {     make.left.top.equalTo(@100);     make.size.mas_equalTo(CGSizeMake(50, 50)); }];

1.创建maker

首先调用mas_makeConstraints:,这是一个UIView的分类方法,参数是一个设置约束的block,会把调用视图作为参数创业一个maker:

// View+MASAdditions.h - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {     self.translatesAutoresizingMaskIntoConstraints = NO;     // 创建maker,并保存调用该方法的视图view     MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];     block(constraintMaker);     return [constraintMaker install]; }

2.生成约束

接下来,开始利用maker产生约束,即调用block(constraintMaker)

2.1 设置坐标x

make.left

调用过程:

// MASConstraintMaker.h - (MASConstraint *)left {     return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft]; } - (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 isKindOfClass:MASViewConstraint.class]) {         // 传入的参数是nil,所以此处代码不会执行         ...     }     if (!constraint) {         // 设置newConstraint的代理为maker         newConstraint.delegate = self;         // 把约束加入到数组中         [self.constraints addObject:newConstraint];     }     // 返回MASViewConstraint类型的约束对象     return newConstraint; }

其中,上述代码根据maker保存的view和传入的约束属性layoutAttribute创建了一个MASViewAttribute对象,然后根据viewAttribute对象创建了一个MASViewConstraint约束对象,代码如下:

// MASViewAttribute.h - (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {     self = [self initWithView:view item:view layoutAttribute:layoutAttribute];     return self; } - (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {     self = [super init];     if (!self) return nil;          // 保存视图view     _view = view;     _item = item;     // 保存约束属性:NSLayoutAttributeLeft     _layoutAttribute = layoutAttribute;          return self; } // MASViewConstraint.h - (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {     self = [super init];     if (!self) return nil;          // 保存第一个属性(封装了视图view和约束属性NSLayoutAttributeLeft)     _firstViewAttribute = firstViewAttribute;     self.layoutPriority = MASLayoutPriorityRequired;     self.layoutMultiplier = 1;          return self; }

2.2 设置坐标y

make.left.top

由于make.left返回的是MASViewConstraint对象,所以调用的top应该是MASViewConstraint类中的方法(该方法继承自父类MASConstraint),调用过程如下:

// MASConstraint.h - (MASConstraint *)top {     // self是MASViewConstraint对象     return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop]; } // MASViewConstraint.h - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {     NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");     // self.delegate是maker对象     return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute]; } // MASConstraintMaker.h - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {     MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];     MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];     if ([constraint isKindOfClass:MASViewConstraint.class]) {         // 由于参数constraint不为nil,所以进入此处执行         //replace with composite constraint         NSArray *children = @[constraint, newConstraint];         // 创建约束集合对象,并把先前的约束对象和本次新创建的约束对象保存到数组中         MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];         // 设置约束集合对象的代理为maker         compositeConstraint.delegate = self;         // 用约束集合对象替换maker中已经保存的约束对象,因为我们同一个maker设置了2个以上的约束         [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];         // 返回MASCompositeConstraint约束集合对象         return compositeConstraint;     }     if (!constraint) {         ...     }     return newConstraint; }

如果一个maker添加多个约束后,就会创建MASCompositeConstraint对象,创建约束集合的过程如下:

- (id)initWithChildren:(NSArray *)children {     self = [super init];     if (!self) return nil;     // 保存约束数组     _childConstraints = [children mutableCopy];     for (MASConstraint *constraint in _childConstraints) {         // 设置数组中所有的约束对象的代理为MASCompositeConstraint对象         constraint.delegate = self;     }     return self; }

在创建了MASCompositeConstraint对象后,就会更新maker中的约束数组,在最后添加约束的时候,就会是全部的约束对象,代码如下:

- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {     NSUInteger index = [self.constraints indexOfObject:constraint];     NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);     [self.constraints replaceObjectAtIndex:index withObject:replacementConstraint]; }

2.3 设置x、y的值

make.left.top.equalTo(@100)

make.left.top返回的对象是MASCompositeConstraint类型,调用过程如下:

// MASConstraint.h - (MASConstraint * (^)(id))equalTo {     return ^id(id attribute) {         return self.equalToWithRelation(attribute, NSLayoutRelationEqual);     }; } // MASCompositeConstraint.h - (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {     return ^id(id attr, NSLayoutRelation relation) {         for (MASConstraint *constraint in self.childConstraints.copy) {             // 遍历数组,把每个MASViewConstraint对象都调用该方法             constraint.equalToWithRelation(attr, relation);         }         return self;     }; } // MASViewConstraint.h - (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {     return ^id(id attribute, NSLayoutRelation relation) {         if ([attribute isKindOfClass:NSArray.class]) {             // 由于attribute是@100的包装类型,不是数组,此处代码不会执行             ...         } else {             NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");             // 设置约束类别为NSLayoutRelationEqual             self.layoutRelation = relation;             // 设置第二个属性             self.secondViewAttribute = attribute;             return self;         }     }; } - (void)setLayoutRelation:(NSLayoutRelation)layoutRelation {     _layoutRelation = layoutRelation;     // 表明已经有了约束关系     self.hasLayoutRelation = YES; }

下面分析一下设置第二个属性secondViewAttribute的过程,因为Masonry重写了setter方法,过程如下:

// MASViewConstraint.h - (void)setSecondViewAttribute:(id)secondViewAttribute {     if ([secondViewAttribute isKindOfClass:NSValue.class]) {         // secondViewAttribute是@100类型         [self setLayoutConstantWithValue:secondViewAttribute];     } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {         // secondViewAttribute是视图UIView类型         _secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];     } else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {         // secondViewAttribute是view.mas_left类型         _secondViewAttribute = secondViewAttribute;     } else {         NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);     } } // MASConstraint.h   @100类型 - (void)setLayoutConstantWithValue:(NSValue *)value {     // 根据value的不同类型,设置不同的属性值     if ([value isKindOfClass:NSNumber.class]) {         self.offset = [(NSNumber *)value doubleValue];     } else if (strcmp(value.objCType, @encode(CGPoint)) == 0) {         CGPoint point;         [value getValue:&point];         self.centerOffset = point;     } else if (strcmp(value.objCType, @encode(CGSize)) == 0) {         CGSize size;         [value getValue:&size];         self.sizeOffset = size;     } else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) {         MASEdgeInsets insets;         [value getValue:&insets];         self.insets = insets;     } else {         NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value);     } }

由于@100是NSNumber类型,所以执行self.offset来设置偏移量,代码如下:

// MASViewConstraint.h - (void)setOffset:(CGFloat)offset {     // 设置layoutConstant属性值,在最后添加属性时作为方法参数传入     self.layoutConstant = offset; } - (void)setLayoutConstant:(CGFloat)layoutConstant {     _layoutConstant = layoutConstant; #if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)     ... #else     self.layoutConstraint.constant = layoutConstant; #endif }

这里有网友疑惑,因为self.layoutConstraint在上面的方法中一直是nil,设置它的constant属性是没有意义的,不知道这么写有何意义?其实,我也有同样的疑问!!!

2.4 设置size

另外,make.size的实现过程和上面的分析类似,有兴趣的可以自行参考,看一看具体的实现过程,在此不做分析。

3.安装约束

下面分析一下约束的安装过程

[constraintMaker install]

调用过程如下:

// MASConstraintMaker.h - (NSArray *)install {     if (self.removeExisting) {         // 是remake,所以要先删除已经视图中存在的约束         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; } // MASViewConstraint.h - (void)uninstall {     if ([self supportsActiveProperty]) {         // 如果 self.layoutConstraint 响应了 isActive 方法并且不为空,会激活这条约束并添加到 mas_installedConstraints 数组中,最后返回         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]; }

下面分析一下install的过程:

- (void)install {     // 如果已经安装过约束,直接返回     if (self.hasBeenInstalled) {         return;     }          // 如果 self.layoutConstraint 响应了 isActive 方法并且不为空,会激活这条约束并添加到 mas_installedConstraints 数组中,最后返回     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;     // alignment attributes must have a secondViewAttribute     // therefore we assume that is refering to superview     // eg make.left.equalTo(@10)     if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {         // 如果第一个属性不是size属性,并且第二个属性为nil,就把第二个视图设置为view的父视图,约束属性设置为view的约束属性         secondLayoutItem = self.firstViewAttribute.view.superview;         secondLayoutAttribute = firstLayoutAttribute;     }          // 创建约束对象     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 (self.secondViewAttribute.view) {         // 如果第二个属性视图存在,就取第一个视图和第二个视图的最小父视图         MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];         NSAssert(closestCommonSuperview,                  @"couldn't find a common superview for %@ and %@",                  self.firstViewAttribute.view, self.secondViewAttribute.view);         self.installedView = closestCommonSuperview;     } else if (self.firstViewAttribute.isSizeAttribute) {         // 如果第一个属性是设置size的,就把第一个视图赋值给installedView         self.installedView = self.firstViewAttribute.view;     } else {         // 否则就取第一个视图的父视图赋值给installedView         self.installedView = self.firstViewAttribute.view.superview;     }     MASLayoutConstraint *existingConstraint = nil;     if (self.updateExisting) {         // 如果是更新属性,就根据layoutConstraint查看视图中是否存在该约束         existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];     }     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];     } }

求两个视图的最小父视图的代码如下:

- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {     MAS_VIEW *closestCommonSuperview = nil;     MAS_VIEW *secondViewSuperview = view;     while (!closestCommonSuperview && secondViewSuperview) {         MAS_VIEW *firstViewSuperview = self;         while (!closestCommonSuperview && firstViewSuperview) {             if (secondViewSuperview == firstViewSuperview) {                 // 如果first和second的视图一样,就设置closestCommonSuperview,并返回                 closestCommonSuperview = secondViewSuperview;             }             firstViewSuperview = firstViewSuperview.superview;         }         secondViewSuperview = secondViewSuperview.superview;     }     return closestCommonSuperview; }

其实,上述代码是先判断first和second的视图是否一样,如果一样,直接返回;如果不一样,就判断fisrt的父视图和second是否一样,如果一样,就返回;不一样,继续判断first的父视图和second的父视图是否一样,如果一样,就返回;不一样,重复迭代。

结束语

Masonry的源码分析完结,如果文中有不足之处,希望指出,互相学习。

正文到此结束
Loading...