先簡(jiǎn)單看一下 Masonry 主要的設(shè)計(jì)以及 Class 結(jié)構(gòu)方法

Masonry 采用了經(jīng)典的 Composite 設(shè)計(jì)模式,如果你還不清楚該設(shè)計(jì)模式,你需要 Google 看看對(duì)應(yīng)的文章
看完了上面類設(shè)計(jì)圖,我們開(kāi)始跟蹤程序
Masonry 開(kāi)始于這樣的代碼結(jié)構(gòu)
- (void)viewDidLoad {
[super viewDidLoad];
UIView *aview = [[UIView alloc] init];
[self.view addSubview:aview];
[aview mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view);
make.left.right.equalTo(@0);
}];
}
其中, mas_makeConstraints 方法在 View+MASAdditions 被定義
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
代碼的第二行創(chuàng)建了一個(gè) MASConstraintMaker(MASConstraintMaker 與 MASConstraint 一樣 定義了 left right 等方法) 對(duì)象,也就是我們能夠進(jìn)行鏈?zhǔn)讲僮?br>
make.left.right.equalTo 開(kāi)始對(duì)象
第三行執(zhí)行代碼 block(constraintMaker), 也就是執(zhí)行我們寫的 make.left.right.equalTo 的代碼
下面開(kāi)始解析這一段鏈?zhǔn)酱a
MASConstraintMaker 內(nèi)部有一個(gè) NSMutableArray<MASConstraint *> *constraints 對(duì)象, constraints 保存了每一條 make 出來(lái)的信息,比如如果你寫這樣的代碼
make.top.equalTo(self.view);
make.left.right.equalTo(@0);
constraints 將會(huì)保存 2 個(gè)對(duì)象,第一個(gè)對(duì)象記錄了 make.top.equalTo(self.view) 的所有信息,第二個(gè)對(duì)象記錄了 make.left.right.equalTo(@0) 的所有信息,而對(duì)象的數(shù)據(jù)結(jié)構(gòu)就是上圖中的 MASViewConstraint 和 MASCompositeConstraint
接下來(lái),我們看一看它是如何保存這個(gè)信息的:
先看第一個(gè)層級(jí):
make.top
調(diào)用順序如下:
- (MASConstraint *)top {
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 isKindOfClass:MASViewConstraint.class]) {
// //replace with composite constraint
// NSArray *children = @[constraint, newConstraint];
// MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
// compositeConstraint.delegate = self;
// [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
// return compositeConstraint;
// }
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
其中上面注釋的代碼是本次調(diào)用不會(huì)運(yùn)行的,后面會(huì)講到注釋掉的代碼的作用
介紹一下 MASViewAttribute
MASViewAttribute 的 Class 結(jié)構(gòu)如下圖

上面的 item 字段,Masonry 上給的是 id 字段,因?yàn)?還有 viewController 的 topLayoutGuide 屬性,這里 為了方便理解,可以把它直接看作 UIView
剩余的代碼很簡(jiǎn)單,只是根據(jù) top 這個(gè)屬性,新建你一個(gè) MASViewConstraint 的對(duì)象,然后為 MASViewConstraint1 創(chuàng)建firstViewAttribute(這里還沒(méi)有生成secondViewAttribute`)
相當(dāng)于下面的代碼:
//self 是外部的 [aView mas_makeConstraints] 的 aView
MASViewConstraint *newConstraint.firstViewAttribute.item = self.view //外部調(diào)用 make.top 的 aview
newConstraint.firstViewAttribute.layoutConstraint = NSLayoutAttributeTop
最后 把 這個(gè) newConstraint 加入到 MASConstraintMaker(make) 的 constraints 對(duì)象中
這樣 完成了 make.top 的操作 (.equalTo 的操作稍后介紹)
接著,我們看第二句
make.left.right
其中,前面半句 make.left 和上面的步驟是一樣的,不同的地方在于后面 .right
首先,前半句 make.left 返回了 MASAttribute(MASViewConstraint)對(duì)象,已經(jīng)不是 MASConstraintMaker(make) 對(duì)象了。所以 我們需要看看 MASViewConstraint 的 left 方法做了那些事情。
調(diào)用順序如下:
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute]; //這里 delegate 指向了剛剛的 `MASConstraintMaker(make)` 對(duì)象
}
- (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;
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
// if (!constraint) {
// newConstraint.delegate = self;
// [self.constraints addObject:newConstraint];
// }
return newConstraint;
}
這一次最后這個(gè)函數(shù)的執(zhí)行了上階段注釋的代碼,這段代碼和之前的代碼大致相同,也是先創(chuàng)建了一個(gè) MASViewConstraint 的對(duì)象, 但是,不同之處在于這次組成了一個(gè) MASCompositeConstraint,MASCompositeConstraint 的 childConstraints 放了上次的 make.left 的信息和這次 make.right 的信息,并且 用 MASCompositeConstraint 替換了原來(lái)的 MASViewConstraint。 (這里希望你理解 Composite 設(shè)計(jì)模式的精髓, 因?yàn)?類似這種make.left.right.top三連以上的鏈?zhǔn)骄涫?MASCompositeConstraint 里面放的是兩個(gè)MASCompositeConstraint)
最后是 equalTo 的語(yǔ)法
先看一下 equalTo 的定義
- (MASConstraint * (^)(id attr))equalTo
equal 是一個(gè) 返回 MASConstraint * 里面包括一個(gè)參數(shù) attr 名為 equalTo 的 block
之所以是 attr 被定義為 id 是因?yàn)?我們可以寫出這樣的代碼
make.left.equalTo(self.view) 或者 make.left.equalTo(@0)
整個(gè) equalTo 的調(diào)用順序如下(self 是 MASConstraint對(duì)象)
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
- (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;
// }
};
}
這里 我只列舉出了 MASViewConstraint 的 equalToWithRelation, 因?yàn)?Composite 模式中定義的 equalToWithRelation 其實(shí)就是讓所有的子類依次去執(zhí)行 equalToWithRelation,另外 我并沒(méi)有怎么使用過(guò) equalTo(attr) attr 是一個(gè) NSArray 類型,所以 注釋的代碼沒(méi)有去鉆研。
在 MASViewConstraint 的 secondViewAttribute 被定義如下
- (void)setSecondViewAttribute:(id)secondViewAttribute {
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
[self setLayoutConstantWithValue:secondViewAttribute];
} else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
_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);
}
}
這也是為什么我們能夠?qū)戇@樣的代碼
make.left.equalTo(@0) // make.left.equalTo(self.view)
equalTo 后面可以接 NSNumber 和 UIView 甚至是 NSValue
好了,到此為止,所有的鏈?zhǔn)酱a已經(jīng)解讀完畢,至于 類似的 offset 道理都一樣,相對(duì)簡(jiǎn)單,這里不再做過(guò)多的陳述
最后是 [constraintMaker install] 的代碼
調(diào)用函數(shù)如下(self 是 MASConstraintMaker):
- (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;
}
上面的代碼 removeExisting 是 mas_remakeConstraints 做的事情, Masonry 用一個(gè) MutableSet 記錄了所有通過(guò) Masonry 創(chuàng)建的 constraint, 這個(gè) set 被定義在了 MASConstraints.m 里面
#define MAS_VIEW UIView
@interface MAS_VIEW (MASConstraints)
@property (nonatomic, readonly) NSMutableSet *mas_installedConstraints;
@end
@implementation MAS_VIEW (MASConstraints)
static char kInstalledConstraintsKey;
- (NSMutableSet *)mas_installedConstraints {
NSMutableSet *constraints = objc_getAssociatedObject(self, &kInstalledConstraintsKey);
if (!constraints) {
constraints = [NSMutableSet set];
objc_setAssociatedObject(self, &kInstalledConstraintsKey, constraints, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return constraints;
}
@end
每次通過(guò) Masonry 添加最后添加成功的 constraint 都會(huì)被加入到這個(gè) set 里面
函數(shù)的最后把所有添加到 MASConstraintMaker 的 constraints 的數(shù)組清空(因?yàn)樗械?constraint 都已經(jīng)被加入到了 View 里)
最后 我們看看 install 的代碼
NSArray *constraints = self.constraints.copy; //這里為什么用 copy 我也不是很清楚作者是怎么想的,可能是出于線程安全的考慮
for (MASConstraint *constraint in constraints) {
constraint.updateExisting = self.updateExisting;
[constraint install];
}
[constraint install] 也是利用了 Composite 設(shè)計(jì)模式的特性(希望你能真正理解 Composite )
MASViewConstraint 的 install 實(shí)現(xiàn):
- (void)install {
if (self.hasBeenInstalled) {
return;
}
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) {
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
////---------截止這里的代碼都很好理解 就是在配好 NSLayoutConstraint 的所有 item 和 attribute, 而這些東西在之前的 make 鏈?zhǔn)秸Z(yǔ)法都組裝好了---------////
////---------其中 masonry 自己的注釋也寫明白了 為什么 make.left.equalTo(@10) 這樣的代碼能夠被組裝成 NSLayoutConstraint---------////
//MASLayoutConstraint 可以簡(jiǎn)單的認(rèn)為就是 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 (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
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
} else {
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}
這段代碼是 install 的最后一個(gè)步驟,整體來(lái)說(shuō)比較簡(jiǎn)單,就是把 MASViewConstraint 變成 NSLayoutConstraint 加入到具體的 UIView 當(dāng)中,如果你還不清楚怎么用系統(tǒng)的 API 寫 AutoLayout 的話,可以點(diǎn)這里。
其中 mas_closestCommonSuperview 是找 firstViewAttribute.view 和 secondViewAttribute.view 的共同公共的父 View, 這里的算法很簡(jiǎn)單,只是單純的遍歷
- (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 的 mas_makeConstraints 的整個(gè)過(guò)程全部分析完畢,對(duì)于剩下的 mas_remakeConstraints 和 mas_updateConstraints 大同小異,這里不再做更多陳述