内容简介:首先,给一个 view 添加 AutoLayout 的原生方法:上文添加的约束,使用 Masonry 如下,简化了大概 35 行代码:mas_makeConstraints 内部逻辑展开如下:
首先,给一个 view 添加 AutoLayout 的原生方法:
- 使用 NSLayoutAttribute 创建一个 NSLayoutConstraint 对象;
- 然后 [view addConstraint:]
UIView *superView = self.view; UIView *view1 = [UIView new]; [superView addSubView:view1]; // 禁用自动约束 view1.translatesAutoresizingMaskIntoConstraints = NO; // 创建一个 view1 相对于 superView 的 left 约束 NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:superView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:10]; // 省略 30 行 NSLayoutConstraint *rightConstraint; NSLayoutConstraint *topConstraint; NSLayoutConstraint *bottomConstraint; [view1 addConstraint:leftConstraint]; [view1 addConstraint:rightConstraint]; // 省略... 复制代码
使用 Masonry 添加 AutoLayout 约束
上文添加的约束,使用 Masonry 如下,简化了大概 35 行代码:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(superView).offset(10); }]; 复制代码
Masonry 添加约束流程解析
mas_makeConstraints 内部逻辑展开如下:
[view1 mas_makeConstraints:^(...) { view1.translatesAutoresizingMaskIntoConstraints = NO; MASConstraintMaker *maker = [[MASConstraintMaker alloc] initWithView:view1]; // 调用 block(): maker.left.equalTo(0); maker.width.and.top.equalTo().offset(2); [maker install]; }] 复制代码
可以从上述例子中看到,Masonry 添加约束调用的 [view1 mas_makeConstraints:] 里,主要做了以下事情:
- 禁用 translatesAutoresizingMaskIntoConstraints 自动约束;
- 初始化一个 ConstraintMaker (约束工厂类),并给 maker 添加 上下左右/宽高 的约束(注意这一步只是将约束记录到 maker 的 constraint array 中);
- 在 maker install 时,将 constraint array 中的约束逐个 add 到 view 上;
1:生成约束(并 record)
1.1 make.left
// 语法分析: MASConstraintMaker *make = [MASConstraintMaker new]; // make.left 只是 get 方法( MASConstraintMaker 的 MASConstraint* left 属性) MASConstraint *mas = make.left; 复制代码
- 可以看到 make.left 是调用 .left 属性;
- 但由于重写了 left 属性的 get 方法,所以,make.left 会调用 [make addConstraint:] 添加一条 NSLayoutAttributeLeft 类型的约束;
- left 属性的 get 方法,最终 return 了一个 MASConstraint 对象,所以后续才能链式调用;
// MASConstraintMaker.m - (MASConstraint *)left { return [self constraint:nil addConstraintWithLayoutAttribute:NSLayoutAttributeLeft]; } - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { // 用 view 和 NSLayoutAttribute 构建 MASViewAttribute MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute]; // 用 MASViewAttribute 构建 MASViewConstraint MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute]; // ... 省略 if (!constraint) { // 作用?? newConstraint.delegate = self; // 将 MASViewConstraint 添加到 make.constraints(Array) 中,等 install 时再将约束 add 到 view 上 [self.constraints addObject:newConstraint]; } // return 的 MASViewConstraint 可以继续 链式调用 return newConstraint; } 复制代码
1.2 make.left.and
MASConstraint *mas = make.left;
中可以看到 make.left
返回的是一个 MASConstraint *
类型。
make.left.and.with
and 和 with 作为 get 方法被调用,里边只是简单的 return 了 MASConstraint self,所以可以继续链式调用
1.3 make.left.and.width
MASConstraint *mas = make.left;
中可以看到 make.left
返回的是一个 MASConstraint *
类型。因此,链式调用到 .width 时,调用的不再是 maker 的方法,而是 MASConstraint
的方法:
// MASConstraint.m - (MASConstraint *)width { return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth]; } 复制代码
MASConstraint
是一个 Abstract 抽象类:只提供接口,内部是空实现,需要子类处理。因此 width 里调用的 addConstraintWithLayoutAttribute:
会调用到子类 MASViewConstraint
中:
// MASViewConstraint.m - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute]; } 复制代码
MASViewConstraint
中并没有生成或添加约束,只是调用 delegate 去处理。这个 delegate
刚好就是前边的 maker
对象,所以又回到了上一步 make.left
一样的逻辑(注意上一步中省略掉没贴的代码):
// MASConstraintMaker.m - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { // 同 make.left ,省略... // MASViewAttribute *viewAttribute = ; // MASViewConstraint *newConstraint = ; // make.left 时,此方法传入的 constraint 是 nil,因此分析 make.left 时,这块代码省略了 // make.left.width 到 .width 时,这里传入的 constraint 就是 make.left 生成的 newConstraint 对象 if ([constraint isKindOfClass:MASViewConstraint.class]) { // 注: 此处的 constraint 即 left constraint;newConstraint 即 width constraint NSArray *children = @[constraint, newConstraint]; // MASCompositeConstraint 是 MASConstraint 的子类,constraint group MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children]; compositeConstraint.delegate = self; // 替换约束: // 替换已经记录到 maker.constraintArray 中的 left constraint 约束为 constraint group [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint]; return compositeConstraint; } // ... 省略 // if 条件成立,已经 return,后续不会再重复 add constraint 到 array } // array 替换元素 - (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint { NSUInteger index = [self.constraints indexOfObject:constraint]; [self.constraints replaceObjectAtIndex:index withObject:replacementConstraint]; } 复制代码
1.4 make.left.and.width.equalTo()
用于 make.left 作为 get 方法返回的是 MASViewConstraint
对象,可以继续链式调用: make.left.equalTo(superView)
,即调用 MASViewConstraint.equalTo();
equalTo
入参是 id 类型的 attribute, attribute
类型可以是 MASViewAttribute、UIView、NSValue
,equalTo 方法内部对类型做了 if 判断;
equalTo 是个宏定义,展开后的调用链是: qualTo(x) -> mas_equalTo(x) -> MASViewConstraint.equalToWithRelation(x, NSLayoutRelationEqual)
注意:
- 这里调用过程中将 NSLayoutRelation 参数也传入到了 MASViewConstraint.equalToWithRelation();
-
equalToWithRelation 中保存了 equalTo 传入的
id 类型 attribute
和NSLayoutRelation
到 MASViewConstraint 对象中,并返回 MASViewConstraint 对象,以继续链式调用;
// MASViewConstraint.m - (MASConstraint * (^)(id))mas_equalTo { return ^id(id attribute) { return self.equalToWithRelation(attribute, NSLayoutRelationEqual); }; } - (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation { return ^id(id attribute, NSLayoutRelation relation) { // ... 简化 // 保存了 equalTo 传入的 attribute 和 NSLayoutRelation 类型 self.layoutRelation = relation; self.secondViewAttribute = attribute; // 继续返回 MASConstraint,链式调用 return self; }; } 复制代码
1.5 make.left.equalTo().offset()
继续链式调用 offset: make.left.equalTo(superView).offset(2);
注:
-
offset
和equalTo
不同,不是像equalTo
有入参的,所以可以加括号 equalTo(xxx); -
offset
方法是一个无入参方法,但是方法的返回值是一个有入参有返回值的 block 类型,因此可以 .offset(3) 调用; - 并且,由于 block 的返回值仍是 MASConstraint 对象,所以可以继续链式调用;
简化逻辑如下:
// make.left 是 get 方法 MASConstraint* mas = make.left; MASConstraint*(^block)(CGFloat f) = mas.offset; // 由于 .offset 的 返回值是 block,所以,可以直接调用: mas.offset(2); // mas.offset(2) 等同于: block(2); // block 的返回值 MASConstraint *mas2 = mas.offset(2); 复制代码
1.6 make.xxx
-
其它
make.center
make.insets.and.size
都是一样的用法; -
make.left.priority(MASLayoutPriorityDefaultLow)
等于make.left.priorityLow
;
2:添加约束
2.1 [make install]
对 MASConstraintMaker *make 添加约束后,make install 最后执行约束:
// MASConstraintMaker.m - (NSArray *)install { // mas_remakeConstraints 的逻辑:先对已加的约束全部移除,再重新添加 if (self.removeExisting) { NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view]; for (MASConstraint *constraint in installedConstraints) { [constraint uninstall]; } } NSArray *constraints = self.constraints.copy; // 遍历 make 里添加的每个 MASConstraint 并 install for (MASConstraint *constraint in constraints) { // mas_updateConstraints 的逻辑: 标记是否需要更新,稍后在 MASViewConstraint install 里更新已加的约束 constraint.updateExisting = self.updateExisting; // 最终 install 约束的还是在 MASViewConstraint 里 [constraint install]; } [self.constraints removeAllObjects]; return constraints; } 复制代码
2.2 [constraint install]
// MASViewConstraint.m - (void)install { // ... 省略:避免重复添加的逻辑 // 取出 MASViewConstraint 记录的 两个 MASViewAttribute 对象 MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item; NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute; MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item; NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute; // ... 省略 // 1、构建一个 NSLayoutAttribute MASLayoutConstraint *layoutConstraint = [MASLayoutConstraint constraintWithItem:firstLayoutItem attribute:firstLayoutAttribute relatedBy:self.layoutRelation toItem:secondLayoutItem attribute:secondLayoutAttribute multiplier:self.layoutMultiplier constant:self.layoutConstant]; // ... // 创建完约束对象后,寻找约束该添加到那个View上: if (self.secondViewAttribute.view) { // 如果是两个视图相对约束,就获取两种的公共父视图。 MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view]; self.installedView = closestCommonSuperview; } else if (self.firstViewAttribute.isSizeAttribute) { // 如果添加的是Width或者Height,那么就添加到当前视图上 self.installedView = self.firstViewAttribute.view; } else { // 如果既没有指定相对视图,也不是Size类型的约束,那么就将该约束对象添加到当前视图的父视图上 self.installedView = self.firstViewAttribute.view.superview; } // 是否已经添加了约束 MASLayoutConstraint *existingConstraint = nil; // mas_updateConstraints 的逻辑 if (self.updateExisting) { existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint]; } if (existingConstraint) { // 存在,直接重新赋值 existingConstraint.constant = layoutConstraint.constant; // ... } else { // 不存在,添加 // 2、[view addConstraint:NSLayoutAttribute]; !!! [self.installedView addConstraint:layoutConstraint]; // ... } } 复制代码
代码技巧
block 灵活使用
block 写法
@property (nonatomic, strong) UIView *(^myBlock)(NSLayoutAttribute attr); self.myBlock = ^UIView *(NSLayoutAttribute attr) { return greenView; }; // blcok 在右侧时,^ 在最前 dispatch_block_t t = ^void(void) { }; UIView *(^block)(NSLayoutAttribute attr) = self.myBlock; self.myBlock = block; 复制代码
更多 block 语法,见: How Do I Declare A Block in Objective-C?
block 作为入参简化外部调用
在方法需要传入一个 ConfigModel 时,使用 block 作为入参:让外部无需关注 ConfigModel 初始化方式,只需要关注配置项
// 使用: [view mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(lastView).offset(2); }]; - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block { // new 一个 maker,然后调用 block 传出去 MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; // 外部对 maker 配置后,maker install block(constraintMaker); return [constraintMaker install]; } 复制代码
应用案例: SDK 初始化
[SHWAccountSDK setupConfig:^(SHWAccountConfig *config) { config.appKey = @"1"; config.secret = @"2"; }]; 复制代码
block 入参作为 result 回调
[SHWAccountSDK getSMSCode:phoneNum success:^(BOOL isSuccess) { // getSMSCode 内部 new 了一个 Request 对象; } failure:^(NSString *errCode, NSString *errMsg) { }]; MyRequest *request; [request startWithSuccess:^(BOOL isSuccess) { NSLog(@"getSMSCodeAsyncWithPhoneNum success"); } failure:^(NSString *errCode, NSString *errMsg) { NSLog(@"getSMSCodeAsyncWithPhoneNum fail"); }]; 复制代码
block 作为返回值,链式调用
通常函数的入参是 block 用的比较多,但函数的返回值是 block 时,可以写出链式调用的优雅写法:
- (MASConstraint * (^)(CGFloat))offset { return ^id(CGFloat offset){ self.offset = offset; return self; }; } // 使用: [blueView mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(superview.mas_bottom).offset(padding); }]; 复制代码
原理:
MASConstraint *mas = [MASConstraint new]; MASConstraint*(^block)(CGFloat f) = mas.offset; // 以下相等 block(2); mas.offset(2); // 由于 mas.offset 的返回值是一个 (入参是 CGFloat 的)block,在后边直接追加 (2),相当于调用 block // 并且,由于 block 的返回值仍是 mas 类型,所以后边可以继续链式调用 复制代码
对比:
// 返回值不是 block,只是 self class 时 - (MASConstraint *)setOffset:(CGFloat)offset { self.offset = offset; return self; } // 使用: MASConstraint *mas = [MASConstraint new]; // 只能这样调用 [[mas setOffset:3] setOffset:1]; 复制代码
MASBoxValue: mas_equalTo、equalTo
mas_equalTo()
可以传入多种类型入参:·
[view1 makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(view2); make.left.mas_equalTo(3); make.left.mas_equalTo(@(2)); make.left.mas_equalTo(view2.mas_left); make.size.mas_equalTo(CGSizeMake(10, 10)); make.edges.equalTo(UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f)); make.height.equalTo(@[redView, blueView]) }]; 复制代码
其定义如下:
#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value)) #define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__))) - (MASConstraint * (^)(id))equalTo { return ^id(id attribute) { return ...; }; } // 尽管传入的是 id 类型,但是解析也还是要支持变参和,并将 float,double,int 这样的值类型数据转换成和 equalTo 一样的对象 NSNumber 数据: static inline id _MASBoxValue(const char *type, ...) { va_list v; va_start(v, type); id obj = nil; if (strcmp(type, @encode(id)) == 0) { id actual = va_arg(v, id); obj = actual; } else if (strcmp(type, @encode(CGPoint)) == 0) { CGPoint actual = (CGPoint)va_arg(v, CGPoint); obj = [NSValue value:&actual withObjCType:type]; } else if (strcmp(type, @encode(CGSize)) == 0) { CGSize actual = (CGSize)va_arg(v, CGSize); obj = [NSValue value:&actual withObjCType:type]; } else if (strcmp(type, @encode(MASEdgeInsets)) == 0) { MASEdgeInsets actual = (MASEdgeInsets)va_arg(v, MASEdgeInsets); obj = [NSValue value:&actual withObjCType:type]; } else if (strcmp(type, @encode(int)) == 0) { int actual = (int)va_arg(v, int); obj = [NSNumber numberWithInt:actual]; } // ... 省略 va_end(v); return obj; } 复制代码
这也顺便解释了 mas_equalTo
和 equalTo
的区别:没有区别, mas_equalTo
调用的还是 equalTo
,只是调用前对入参进行了 boxValue
转换类型
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- ReactNative源码解析-初识源码
- Spring源码系列:BeanDefinition源码解析
- Spring源码分析:AOP源码解析(下篇)
- Spring源码分析:AOP源码解析(上篇)
- 注册中心 Eureka 源码解析 —— EndPoint 与 解析器
- 新一代Json解析库Moshi源码解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
计算机图形学与几何造型导论
戈德曼 / 邓建松 / 2011-9 / 69.00元
《世界著名计算机教材精选:计算机图形学与几何造型导论》是世界著名计算机教材精选之一。《世界著名计算机教材精选:计算机图形学与几何造型导论》共四部分三十章节,内容包括乌龟绘图,应用递归乌龟程序生成分形,分形的奇特性质,仿射变换,仿射几何:二维计算机图形学的连点过程,应用迭代函数系统生成分形,不动点定理及其推论,递归乌龟程序与共形迭代函数系统等。《世界著名计算机教材精选:计算机图形学与几何造型导论》可......一起来看看 《计算机图形学与几何造型导论》 这本书的介绍吧!