Masonry源码其实非常简单,就是对AutoLayout的简单封装,但是使用中有几处要注意的地方,所以现在就Masonry的源码进行简要的分析。
1、AutoLayout的约束设置
[NASLayoutConstraint constraintWithItem:
attribute:
relatedBy:
toItem:
attribute:
multiplier:
constant:];
加上一个priority,可以看到如果要设置view的约束,总共需要八个参数。
在Masonry中,这八个参数都用一个类MASConstraint存储起来。
MASConstraint有两个子类:
1、MASViewConstraint --- 针对单个约束的设置;
2、MASCompositeConstraint -- 针对一组约束的设置;
通常情况下我们只用到MASViewConstraint,因此对于MASCompositeConstraint 就不再详细讨论了。
2、一些基础的知识讲解
在AutoLayout中,我们知道只有当两个view之间存在公共祖先,才能设置它们之间的约束关系。
而view有一个参数superview,即指向父类的指针 -- 因此这个问题就可以转化为求两个链表中的相交点。
2.1、正常情况下的求两个链表中的相交点
这个问题就很简单了-- 遍历两个链表,求出它们的各自长度L1/L2,然后长的先走
|| L1-L2 ||步,最后再一起走下去,判断是否相等即可。
显然,算法的复杂度为O(n)。
2.2、Masonry中的求两个链表中的相交点
- (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) {
closestCommonSuperview = secondViewSuperview;
}
firstViewSuperview = firstViewSuperview.superview;
}
secondViewSuperview = secondViewSuperview.superview;
}
return closestCommonSuperview;
}
显然,Masonry并没有按照常规的思路,而是直接两两比较,算法复杂度为O(n^2),为什么Masonry没有采取#2.1的做法,个人看法是:我们在设置约束的时候,通常两个view之间的父类都是靠得非常近的,因此反而#2.2的做法比#2.1的高效。
2.3、关于优先级
typedef UILayoutPriority MASLayoutPriority;
static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
无论是在AutoLayout中,还是Masonry中,默认的优先级都是UILayoutPriorityRequired,即最高优先级。
这个在MASViewConstraint初始化中即有体现:
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
self = [super init];
if (!self) return nil;
_firstViewAttribute = firstViewAttribute;
self.layoutPriority = MASLayoutPriorityRequired;
self.layoutMultiplier = 1;
return self;
}
3、Masonry三个布局方法的源码分析
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker); //#1
return [constraintMaker install];
}
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.updateExisting = YES; //#2
block(constraintMaker);
return [constraintMaker install];
}
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
constraintMaker.removeExisting = YES; //#3
block(constraintMaker);
return [constraintMaker install]; //#4
}
mas_makeConstraints
mas_updateConstraints
mas_remakeConstraints
上面三个方法的实现基本是一样的,区别于细节决定了它们的功能。
3.1、MASConstraintMaker包装类
代码#1处就是block的链式调用,这也是Masonry里面比较有趣的东西,各种make。
MASConstraintMaker是一个包装类,它存储了view设置的各种约束。
然后在#4处间接跳到了MASViewConstraint的install方法,这是Masonry最关键的方法了。
3.2、MASViewConstraint中的install方法
- (void)install {
if (self.hasBeenInstalled) { //#5
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) { //#6
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
MASLayoutConstraint *layoutConstraint //#7
= [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; //#8
if (self.secondViewAttribute.view) { //#9
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) { //#10
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant; //#11
self.layoutConstraint = existingConstraint;
} else {
[self.installedView addConstraint:layoutConstraint]; //#12
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}
代码#5处表示的是该约束是否处于激活的状态,可以从#13和#14中看出,其实就是表示NSLayoutConstraint的isActive属性,一般情况下新建的约束,active属性默认是NO。
- (BOOL)supportsActiveProperty {
return [self.layoutConstraint respondsToSelector:@selector(isActive)];
}
- (BOOL)isActive {
BOOL active = YES;
if ([self supportsActiveProperty]) {
active = [self.layoutConstraint isActive]; //#14
}
return active;
}
- (BOOL)hasBeenInstalled {
return (self.layoutConstraint != nil) && [self isActive]; //#13
}
代码#6处表示如果设置的是长度或者宽度,则secondLayoutItem设置为父类。
在判断两个MASViewAttribute是否相等的时候,重载了isEqual和hash方法,isEqual主要是==判断的时候使用,而hash则是在NSDictionary/NSSet等需要hash算法的时候调用。
- (BOOL)isSizeAttribute {
return self.layoutAttribute == NSLayoutAttributeWidth
|| self.layoutAttribute == NSLayoutAttributeHeight; //#14
}
- (BOOL)isEqual:(MASViewAttribute *)viewAttribute {
if ([viewAttribute isKindOfClass:self.class]) { //#15
return self.view == viewAttribute.view
&& self.layoutAttribute == viewAttribute.layoutAttribute;
}
return [super isEqual:viewAttribute];
}
- (NSUInteger)hash { //#16
return MAS_NSUINTROTATE([self.view hash], MAS_NSUINT_BIT / 2) ^ self.layoutAttribute;
}
代码#7就是直接调用AutoLayout的初始化约束方法;
代码#9处找到要设置约束的view;
代码#10处即是mas_updateConstraints和其余两个方法的区别,在#11处,如果mas_updateConstraints,即会更新原有的约束;
但是要格外注意的是layoutConstraintSimilarTo这个方法,如果我们在调用mas_updateConstraints中,要update的属性firstItem/secondItem/firstAttribute/secondAttribute/relation/multiplier/priority这7个属性不尽相同,那么就会跳到#12,添加了一个重复的约束,AutoLayout一般情况下都会报错。因此要谨记:
mas_updateConstraints只是更新约束的constant值,其他不变,也不应该变化,如果变了,那么请不要用mas_updateConstraints,而应该考虑mas_remakeConstraints。
如果要更新的约束不存在,那么mas_updateConstraints就会退化成mas_makeConstraints。
- (MASLayoutConstraint *)layoutConstraintSimilarTo:(MASLayoutConstraint *)layoutConstraint {
// check if any constraints are the same apart from the only mutable property constant
// go through constraints in reverse as we do not want to match auto-resizing or interface builder constraints
// and they are likely to be added first.
for (NSLayoutConstraint *existingConstraint in self.installedView.constraints.reverseObjectEnumerator) {
if (![existingConstraint isKindOfClass:MASLayoutConstraint.class]) continue;
if (existingConstraint.firstItem != layoutConstraint.firstItem) continue;
if (existingConstraint.secondItem != layoutConstraint.secondItem) continue;
if (existingConstraint.firstAttribute != layoutConstraint.firstAttribute) continue;
if (existingConstraint.secondAttribute != layoutConstraint.secondAttribute) continue;
if (existingConstraint.relation != layoutConstraint.relation) continue;
if (existingConstraint.multiplier != layoutConstraint.multiplier) continue;
if (existingConstraint.priority != layoutConstraint.priority) continue;
return (id)existingConstraint;
}
return nil;
}
3.3、关于mas_remakeConstraints
mas_remakeConstraints会在MASConstraintMaker调用install的时候移除原来所有的约束,然后下面的步骤和mas_makeConstraints是一样的。就不在详说了。
- (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;
}
3.4、关于mas_makeConstraints
mas_makeConstraints的源码在上面分析install的时候已经分析清楚了。但是有一点要注意的是:尽量不要对同一个view连续使用mas_makeConstraints进行约束设置,虽然对于没有添加过的约束,也是会设置成功的,但是对于同一个约束,那肯定是会报错的。
网友评论