iOS开发Masonry框架源码解析
iOS开发Masonry框架源码解析
前言:这个框架编程思想主要包括链式编程
这是一个iOS在控件布局中的轻量级框架,简化了NSLayoutConstraint的使用方式,让我们用链式变成的思想进行对View控件的约束。
本篇主要围绕Masonry框架源码进行解析,从而透析Masonry是如何对NSLayoutConstraint进行封装的。
####1.OC中的链式编程是如何实现的?
链式编程的好处:
点语法+事物+串联
1.简化代码
2.代码可读性高
调用:
self.where.select;
~~~
- (ViewController*)where{
return self;
}
- (void)select{
}
~~~
用getter+有参数的时候应该如何写呢?
~~~
- (ViewController*)where{
return self;
}
//block有保存代码块的特性
- (void(^)(NSString*))select{
void(^block)(NSString *word) = ^(NSString *str){
};
}
~~~
举个栗子:self.where.select(@"参数")。
#####2.框架解析正文
Masonry框架和NSLayoutConstraint调用的对比:
###实际应用:
![avatar](images/Masonry类结构图.png)
#####1.对谁做约束
View+MASAdditions 就是 Masonry 的一个外部的入口,实质上就是 UIView 的一个 Category 作用就是用来设置 MASViewAttribute 的属性,并实例化,并且指定当前的 UIView 对应的约束(LayoutAttribute)
MASUtilities.h 主要是这里除了平台相关代码外,还有些宏的定义和静态方法。Masonry 也对 iOS 和 macOS 做了兼容,在 macOS 里就是 NSView。
其中静态方法
```
static inline id _MASBoxValue(const char *type, ...)
```
是我们经常使用的 mas_equalTo 这个方法,这里可以看到它是如何支持变参和如何将 float,double,int 这样的值类型数据转换成和 equalTo 一样的对象 NSNumber 数据的。这个写法灵感来自GitHub [- specta/expecta: A Matcher Framework for Objective-C/Cocoa ](https://github.com/specta/expecta)。 mas_equalTo 和 equalTo 都是宏定义的。
```
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))
#define equalTo(...) mas_equalTo(__VA_ARGS__)
```
方法实现:
```
- (MASConstraint * (^)(id))equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
- (MASConstraint * (^)(id))mas_equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
```
宏定义不同但是实现方法相同这样写就是避免宏定义冲突的一种方式。
mas_makeConstraints 的的 block 参数会将创建的 MASConstraintMaker 这个工厂类对象暴露出去,让我们去设置这个类对象中的 MASConstraint 属性,然后通过该对象的 install 方法将当前视图中所有添加的约束添加到一个数组里。该数组里存储是 MASViewConstraint 对象,对应的就是 NSLayoutConstraint。
```
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;
```
```
//创建约束
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
//去掉自动autoresizing转为约束的
self.translatesAutoresizingMaskIntoConstraints = NO;
//构建builder
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
//运行builder
block(constraintMaker);
//附值约束返回
return [constraintMaker install];
}
```
#####2.如何做约束
Masonry通过MASConstraintMaker来创建MASConstraint对象。里面有个 constraints 数组专门用来存储创建的这些对象。前面 mas_makeConstraints 的那个 Block 暴露出的就是 MASConstraintMaker 对象。
```
//property 重写了getter方法,使得每次链式调用相当于构造一个约束
@property (nonatomic, strong, readonly) MASConstraint *left;
```
```
//重写getter方法,调用.方法相当于构造约束
- (MASConstraint *)left {
return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
```
```
//通过NSLayoutAttribute添加约束
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
//构造view的MASViewAttribute
MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
//通过MASViewAttribute构造第一个MASViewConstraint
MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
//如果存在contraint,则把constraint和newConstraint组合成MASCompositeConstraint
if ([constraint isKindOfClass:MASViewConstraint.class]) {
//replace with composite constraint
NSArray *children = @[constraint, newConstraint];
MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
compositeConstraint.delegate = self;
//替换原来的constraint成新的MASCompositeConstraint
[self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
return compositeConstraint;
}
//不存在则设置constraint到self.constraints
if (!constraint) {
newConstraint.delegate = self;
[self.constraints addObject:newConstraint];
}
return newConstraint;
}
```
这里会发现每次 getter 都会创建一个新的 MASViewConstraint 对象,这里通过将新的 MASViewConstraint 对象的 delegate 设置成自己的方式让新对象也能够调用相同的方法创建一个新的 MASViewConstraint 对象,使得能够支持进行链式的调用。
#####约束收集完成之后完成之后的处理
MASViewConstraint这个类是对 NSLayoutConstriant 的封装。它的父类是 MASConstraint,MASConstraint 是一个抽象不可实例的类,里面有接口和协议。它的兄弟类是 MASCompositeConstraint,里面有个数组专门存储 MASViewConstraint 对象。
```
//附值约束返回
return [constraintMaker install];
```
```
- (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) {
//设置更新key
constraint.updateExisting = self.updateExisting;
[constraint install];
}
//去除所有缓存的约束结构体
[self.constraints removeAllObjects];
return constraints;
}
```
```
- (void)install {
//已经有约束
if (self.hasBeenInstalled) {
return;
}
//支持active且已经有了约束
if ([self supportsActiveProperty] && self.layoutConstraint) {
//激活约束
self.layoutConstraint.active = YES;
//添加约束缓存
[self.firstViewAttribute.view.mas_installedConstraints addObject:self];
return;
}
//获得item1,attribute1,item2,attribute2
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)
//如果attribute是sizeattribute并且没有第二个attribute
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
//默认设置item2为superview
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];
//设置priority和mas_key
layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;
//如果第二个attribute有view对象
if (self.secondViewAttribute.view) {
//则获取两个view的最小公共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);
//设置约束view为此view
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) {
//如果是size attribute则为本身
self.installedView = self.firstViewAttribute.view;
} else {
//其它则是superview
self.installedView = self.firstViewAttribute.view.superview;
}
//已经存在的约束
MASLayoutConstraint *existingConstraint = nil;
//需要更新
if (self.updateExisting) {
//则获得此生成的约束,返回和installedview的约束是同类的约束
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];
}
}
```
总结:
###参考资料
1.[Apple-NSLayoutConstraint](https://developer.apple.com/reference/uikit/nslayoutconstraint)
2.[Auto Layout Guide](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/index.html)
3.[Visual Format Language](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/VisualFormatLanguage.html)
3.[深入剖析Auto Layout,分析iOS各版本新增特性](http://www.starming.com/index.php?v=index&view=84&utm_source=tuicool&utm_medium=referral)
4.[iOS开发之Masonry框架源码深度解析](http://www.cnblogs.com/ludashi/p/5591572.html)
5.[Masonry源代码分析](http://blog.csdn.net/colorapp/article/details/45030163)
6.[史上比较用心的纯代码实现 AutoLayout](http://ios.jobbole.com/85829/)
7.[iOS开发之Masonry框架源码解析](https://www.cnblogs.com/ludashi/p/5591572.html)