> 文章列表 > 【iOS】—— Masonry源码学习(浅看,未完)

【iOS】—— Masonry源码学习(浅看,未完)

【iOS】—— Masonry源码学习(浅看,未完)

Masonry

文章目录

  • Masonry
    • NSLayoutConstraint用法
    • Masonry源码

Masonry在我们之前的学习中是一个非常有用的第三方库。

Masonry是一种基于Objective-C语言的轻量级布局框架,它可以简化iOS应用程序中的自动布局任务。Masonry提供了一个方便的API,可以编写更加简洁和易于阅读的代码来创建约束。使用Masonry,您可以通过链式调用方法来设置视图之间的约束关系,而无需直接操作NSLayoutConstraint对象。

简单来说,Masonry就是封装了NSLayoutConstraint对象
在学习源码之前,我们先来看看NSLayoutConstraint的用法:

NSLayoutConstraint用法

在 Objective-C 中,NSLayoutConstraint 是一种用于定义视图之间关系的类。它可以让开发者通过代码的方式来描述视图之间的布局约束,从而实现自动化布局。通过使用 NSLayoutConstraint,开发者可以使得 iOS 应用在不同尺寸和方向的设备上都能够呈现出良好的布局效果。

NSLayoutConstraint 可以用来描述视图距离、大小、比例等方面的约束条件,例如一个视图需要与另一个视图保持一定距离,或者两个视图需要保持相同的宽度和高度。

NSLayoutConstraint 的创建通常需要指定两个视图(或者一个视图和父视图)以及一个属性,即要约束的属性。然后,可以设置这个属性的值,以及关于这个属性的其他限制条件。最后,将该约束添加到对应的视图上,即可实现对视图之间布局的控制。

示例:

    // 创建父视图UIView *containerView = [UIView new];containerView.translatesAutoresizingMaskIntoConstraints = YES;containerView.backgroundColor = UIColor.yellowColor;containerView.frame = CGRectMake(100, 100, 200, 200);[self.view addSubview:containerView];// 创建子视图UIView *redView = [UIView new];redView.translatesAutoresizingMaskIntoConstraints = NO;redView.backgroundColor = UIColor.redColor;[containerView addSubview:redView];// 创建约束条件:view2 距离 view1 的四周都有 20 的间距NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:redViewattribute:NSLayoutAttributeRightrelatedBy:NSLayoutRelationEqualtoItem:containerViewattribute:NSLayoutAttributeRightmultiplier:1.0constant:-20];NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:redViewattribute:NSLayoutAttributeBottomrelatedBy:NSLayoutRelationEqualtoItem:containerViewattribute:NSLayoutAttributeBottommultiplier:1.0constant:-20];NSLayoutConstraint *constraint3 = [NSLayoutConstraint constraintWithItem:redViewattribute:NSLayoutAttributeLeftrelatedBy:NSLayoutRelationEqualtoItem:containerViewattribute:NSLayoutAttributeLeftmultiplier:1.0constant:20];NSLayoutConstraint *constraint4 = [NSLayoutConstraint constraintWithItem:redViewattribute:NSLayoutAttributeToprelatedBy:NSLayoutRelationEqualtoItem:containerViewattribute:NSLayoutAttributeTopmultiplier:1.0constant:20];// 将约束条件添加到视图上[self.view addConstraints:@[constraint1, constraint2, constraint3, constraint4]];

展示效果:
【iOS】—— Masonry源码学习(浅看,未完)

我们看到有一个UIView的属性:

translatesAutoresizingMaskIntoConstraints

translatesAutoresizingMaskIntoConstraints是一个布尔属性,用于设置是否自动将视图的AutoresizingMask转换为Autolayout约束。当该属性为true时,UIView的frame和bounds值可以通过直接修改来重新定义视图位置和大小,并且不会影响其Autolayout约束。但是,在使用Autolayout时,为了能够正确地响应任何约束变化,建议将该属性设置为false。

Masonry源码

Masonry中的类名字比较类似,很容易搞混淆,我们先来看看这几个常用的类以及他们之间的关系:

  • MASConstraint:这个类表示一个约束条件,它包含了一个视图和一个属性,以及相关的关系和乘数等信息。
  • MASViewConstraint:这是MASConstraint的子类,表示一个包含两个视图之间的约束条件。
  • MASCompositeConstraint:这个类表示多个约束条件的组合。它可以用来对一组视图应用相同的布局规则。
  • MASConstraintMaker:这个类是用来创建约束条件的入口点。它提供了一组类似于DSL的API,用于创建常见的约束条件,如等宽、等高、上下左右边距等。
  • MASLayoutConstraint:这个类是Masonry内部使用的私有类,它负责将MASConstraint对象转换成NSLayoutConstraint对象,并将其添加到视图上。
  • MASViewAttribute:是一个Masonry框架中的类,用于表示视图的特定属性。它可以用来创建和布局视图之间的约束。

【iOS】—— Masonry源码学习(浅看,未完)

一个最简单的Masonry:

    [view1 mas_makeConstraints:^(MASConstraintMaker *make) {make.width.height.mas_equalTo(@10);make.centerX.mas_equalTo(self.view.mas_centerX);make.top.mas_equalTo(self.view.mas_top).offset(20);}];

我们点进去这个mas_makeConstraints方法:

//View+MASAdditions.h
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {self.translatesAutoresizingMaskIntoConstraints = NO;//这个属性刚刚讲到过,所以用Masonry就可以不用设置这个属性了MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];block(constraintMaker);return [constraintMaker install];
}

在该方法中调用constraintMaker的初始化方法,传给该方法要设置约束的view,生产maker对象,传给block回调出去,然后再调用maker的install方法。

//MASConstraintMaker
- (id)initWithView:(MAS_VIEW *)view {self = [super init];if (!self) return nil;self.view = view;self.constraints = NSMutableArray.new;return self;
}

initWithView:会记录下要设置约束的当前view,同时创建一个数组用来存储即将使用maker添加的约束。在传给mas_makeConstraints方法的block参数中,使用回调出来的maker进行一一添加约束。下面是使用make.width点语法后的全部内部调用过程:

// MASConstraintMaker
- (MASConstraint *)width {return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}- (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]) {//replace with composite constraint// 如果是在已有约束的基础上再创建的约束,则将它们转换成一个 组合约束,并将原约束替换成该组合约束。NSArray *children = @[constraint, newConstraint];MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];compositeConstraint.delegate = self;// 这里会将原来 make.width 添加的约束 替换成一个 组合约束(宽度约束 + 高度约束)[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];// 返回组合约束return compositeConstraint;}if (!constraint) {// 如果不是在已有约束的基础上再创建约束,则添加约束至列表newConstraint.delegate = self;// 注意这一步,会对 make.top.left 这种情形产生关键影响[self.constraints addObject:newConstraint];}return newConstraint;
}

在第二次设置约束时(.height)会进入不同的流程。注意上面提到的newConstraint.delegate设置代理:

//MAConstraint
- (MASConstraint *)height {return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}
//MSViewConstraint
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");//delegate是MASConstraintMakerreturn [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
// MASConstraintMaker
//还是走到刚刚那个方法那里
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {...}

接着来看看.mas_equalTo(@100)的流程:

// MASConstraint
#define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))
- (MASConstraint * (^)(id))equalTo {return ^id(id attribute) {// attribute 可能是 @100 类似的值,也可能是 view.mas_width等这样的return self.equalToWithRelation(attribute, NSLayoutRelationEqual);};
}
- (MASConstraint * (^)(id))mas_equalTo {return ^id(id attribute) {return self.equalToWithRelation(attribute, NSLayoutRelationEqual);};
}
// MASViewConstraint
- (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;}};
}
// 设置约束第二项
- (void)setSecondViewAttribute:(id)secondViewAttribute {if ([secondViewAttribute isKindOfClass:NSValue.class]) {   //数字等[self setLayoutConstantWithValue:secondViewAttribute];} else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {    //frame等_secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];} else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {   //约束对象_secondViewAttribute = secondViewAttribute;} else {NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);}
}
// MASConstraint
- (void)setLayoutConstantWithValue:(NSValue *)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);}
}// MASViewConstraint
- (void)setOffset:(CGFloat)offset {self.layoutConstant = offset;       // 设置约束常量
}
- (void)setSizeOffset:(CGSize)sizeOffset {NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;switch (layoutAttribute) {case NSLayoutAttributeWidth:self.layoutConstant = sizeOffset.width;break;case NSLayoutAttributeHeight:self.layoutConstant = sizeOffset.height;break;default:break;}
}
- (void)setCenterOffset:(CGPoint)centerOffset {NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;switch (layoutAttribute) {case NSLayoutAttributeCenterX:self.layoutConstant = centerOffset.x;break;case NSLayoutAttributeCenterY:self.layoutConstant = centerOffset.y;break;default:break;}
}
- (void)setInsets:(MASEdgeInsets)insets {NSLayoutAttribute layoutAttribute = self.firstViewAttribute.layoutAttribute;switch (layoutAttribute) {case NSLayoutAttributeLeft:case NSLayoutAttributeLeading:self.layoutConstant = insets.left;break;case NSLayoutAttributeTop:self.layoutConstant = insets.top;break;case NSLayoutAttributeBottom:self.layoutConstant = -insets.bottom;break;case NSLayoutAttributeRight:case NSLayoutAttributeTrailing:self.layoutConstant = -insets.right;break;default:break;}
}

再看如果约束对象是一个控件(mas_equalTo(self.view.mas_top)),那么就会走进下面的这段代码:

else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {_secondViewAttribute = secondViewAttribute;
} 

另外,后面的 offset 方法做了一步额外的操作:

// MASConstraint
- (MASConstraint * (^)(CGFloat))offset {return ^id(CGFloat offset){self.offset = offset;return self;};
}
- (void)setOffset:(CGFloat)offset {self.layoutConstant = offset;
}

回到前面,block执行结束之后,调用了[constraintMaker install],下面看一下install方法的实现:

- (NSArray *)install {// 只有在 mas_remakeConstraints 时,removeExisting 才为 YESif (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) {// 设置约束的 updateExisting 属性// 只有在 mas_updateConstraints 时,updateExisting 才为 YESconstraint.updateExisting = self.updateExisting;[constraint install];}// 清空 constraints 数组缓存[self.constraints removeAllObjects];return constraints;
}

install 方法内部会对 constraints 列表中的所有约束依次执行各自的 install 方法来添加约束。我们来看一下约束的 install 方法:

// MASCompositeConstraint
- (void)install {for (MASConstraint *constraint in self.childConstraints) {constraint.updateExisting = self.updateExisting;[constraint install];}
}// MASViewConstraint
- (void)install {// 约束是否已被添加if (self.hasBeenInstalled) {return;}// 如果约束支持 isActive 方法,且 self.layoutConstraint 有值了if ([self supportsActiveProperty] && self.layoutConstraint) {if (@available(iOS 8.0, *)) {self.layoutConstraint.active = YES;} else {// Fallback on earlier versions}[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//对齐属性必须具有第二个ViewAttribute。因此我们假设它指的是超视图// eg make.left.equalTo(@10)if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {secondLayoutItem = self.firstViewAttribute.view.superview;secondLayoutAttribute = firstLayoutAttribute;}// 生成一个 NSLayoutConstraintMASLayoutConstraint *layoutConstraint= [MASLayoutConstraint constraintWithItem:firstLayoutItemattribute:firstLayoutAttributerelatedBy:self.layoutRelationtoItem:secondLayoutItemattribute:secondLayoutAttributemultiplier:self.layoutMultiplierconstant:self.layoutConstant];layoutConstraint.priority = self.layoutPriority;layoutConstraint.mas_key = self.mas_key;// 确定约束layoutConstraint 的约束层级(即要被添加到的位置)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) {self.installedView = self.firstViewAttribute.view;} else {self.installedView = self.firstViewAttribute.view.superview;}MASLayoutConstraint *existingConstraint = nil;if (self.updateExisting) {existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];}if (existingConstraint) {// just update the constant// 约束存在,则更新constant值existingConstraint.constant = layoutConstraint.constant;self.layoutConstraint = existingConstraint;} else {// 约束不存在,则在该位置添加约束[self.installedView addConstraint:layoutConstraint];self.layoutConstraint = layoutConstraint;[firstLayoutItem.mas_installedConstraints addObject:self];}
}

无论是 MASCompositeConstraint 还是 MASViewConstraint,本质上还是调用 MASViewConstraint 的 install 方法。该方法根据 MASViewConstraint 的各个属性创建一个原生的约束(NSLayoutConstraint 类型),并在定位约束层级后,将约束添加到相应层级的视图上。

医疗百科大全