本篇文章是笔者对上篇文章《关于 Masonry 的一些思考》的一些本身的解答,哪里有理解不到位的地方,请尽情拍砖。若是想先看无答案版,请前往上篇文章 《看完 Masonry
源码后的几点思考?》。git
图片来自戴铭文章 《读 SnapKit 和 Masonry 自动布局框架源码》github
Masonry
都作了些什么?Masonry
是一个让开发者用简洁优雅的语法来调用原生 AutoLayout
进行布局的轻量级框架。Masonry
拥有本身的 DSL
布局语言,让咱们能够更具象地描述约束的增长与更新,让约束的代码也变得更加简洁易读、容易理解。编程
DSL 是一种基于特定领域的语言,它使工做更贴近于客户的理解,而不是实现自己,这样有利于开发过程当中,全部参与人员使用同一种语言进行交流。简单来讲,就是咱们只需描述出咱们想要什么效果,而毋需涉及底层实现,这无疑下降了工做过程当中沟通协调的门槛。swift
语言过于苍白,让咱们 show code:api
原生 AutoLayout
实现一个红色 view 布局数组
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[superview addConstraints:@[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],
]];
复制代码
使用 Masonry 进行布局:bash
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];
复制代码
代码甚至能够再精简下:app
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
复制代码
以上代码来自 Masonry 的 github 介绍框架
通过上面的代码比较,Masonry
语法的简洁优雅效果是浅显易见的。代码不只变得精简,并且阅读成本也基本降到了最低。less
[self.view addSubview:btn];
[btn makeConstrants:^(MASLayoutConstraint *make){
make.left.equalTo(self.view).offset(12);
}];
复制代码
答: 不会发生循环引用,方法中 block
参数虽然引用 self.view
,间接持有了 btn
,可是 block
参数是个匿名 block,而且在方法实现里未额外引用这个 block
参数, block
并未被 btn
所持有,也就不存在二者相互持有、循环引用。block -> self.view -> btn -(未引用)- block
而上述方法定义中也明确使用 NS_NOESCAPE
修饰 block
参数, 这个修饰符代表 block
在方法执行完前就会被执行释放,而不会对 block
进行额外的引用保存。
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block
在代码中 Masonry
也确实是这么作的:
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
复制代码
从上面代码中,能够清除地看到,block
参数在 return
以前就被执行,并未被其余对象引用。
更多关于 NS_NOESCAPE 的介绍
额外拓展,不少同窗对
block
的循环引用都不太了解:一样是匿名 block 参数,系统动画
[UIView animateWithDuration:100 animations:^{
NSLog(@"%@",self);
}];
复制代码
不会形成循环引用,而 MJRefresh
的 header
初始化方法
self.scrollView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
NSLog(@"%@", self);
}];
复制代码
为何会形成循环引用?
MJRefresh
的 headerWithRefreshingBlock:
方法内部,返回的 MJRefreshNormalHeader
对象强引用了这个 block
,而这个返回对象最后又被 self.scrollView.mj_header
强引用了,也就形成了 self -> scrollView -> mj_header -> block -> self
的强引用闭环,所以会形成循环引用。
headerWithRefreshingBlock:
实现代码:
+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
{
MJRefreshHeader *cmp = [[self alloc] init];
cmp.refreshingBlock = refreshingBlock;
return cmp;
}
复制代码
系统的动画实现方法中,self
并未和这个 block
产生关联,但 block
确实持有了 self
,但笔者猜想 block
对self 并非强引用,由于若是在这个动画时间内控制器执行 POP
操做,self
会当即被释放掉,也就是说除了导航控制器栈,self
并未被额外的强引用,不然 self 不会被释放。
self 未引用 block (弱)-> self 。所以也不存在循环引用
想想,当方法中使用匿名
block
、匿名对象做为参数,这些匿名对象是被谁持有?会在何时释放呢?欢迎在评论中探讨。
MAS_SHORTHAND
、MAS_SHORTHAND_GLOBALS
宏是作什么用的?它的效果是如何实现的呢?MAS_SHORTHAND
宏能够在调用 Masonry
api
的时候省去 mas_
前缀
Masonry
为 View
定义了 一个 View+MASAdditions
分类。在这个分类中,全部的成员属性和方法都是带有 mas_
前缀的。Masonry 还另外定义了 View+MASShorthandAdditions
分类,在这个分类中全部的全部属性和成员变量都不带 mas_ 前缀。但这个分类被 #ifdef MAS_SHORTHAND #endif
所包裹。 效果以下:
//MASShorthandAdditions 分类
#ifdef MAS_SHORTHAND
...
...(不带有 mas_ 前缀的成员变量和方法)
...
#endif
复制代码
这样只有定义了 MAS_SHORTHAND
以后这个分类才会被编译,而这个分类内部全部属性的 get
方法、对外的接口方法实现仍是调用的带有 mas_
前缀的方法,对于咱们开发者来讲,只是在 mas_
属性与方法外面包裹上了一层语法糖。
不带有 mas_
前缀方法的实现:
//属性的 get 方法宏,在属性前拼接 mas_ 前缀,调用带有前缀的属性 get 方法
#define MAS_ATTR_FORWARD(attr) \
- (MASViewAttribute *)attr { \
return [self mas_##attr]; \
}
复制代码
//不带有 mas_ 前缀的 API,内部会调用带有 mas_ 前缀的 API
- (NSArray *)makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
return [self mas_makeConstraints:block];
}
复制代码
而MAS_SHORTHAND_GLOBALS
宏会将 equalTo()
、 greaterThanOrEqualTo()
、 offset()
宏定义为 mas_equalTo()
、 mas_greaterThanOrEqualTo()
、 mas_offset()
。
而带有 mas_
前缀的方法会将括号内的 block
参数从基本数据类型转化为 NSValue
对象类型
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
#ifdef MAS_SHORTHAND_GLOBALS
#define equalTo(...) mas_equalTo(VA_ARGS)
#define greaterThanOrEqualTo(...) mas_greaterThanOrEqualTo(VA_ARGS)
#define lessThanOrEqualTo(...) mas_lessThanOrEqualTo(VA_ARGS)
#define offset(...) mas_offset(VA_ARGS)
#endif
复制代码
Masonry
的 makeConstraints:
、updateConstraints:
、 remakeConstraints:
有什么区别,分别适合那些场景?- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
复制代码
remakeConstraints:
和上面代码的惟一区别就是增长了constraintMaker.removeExisting = YES;
当 [constraintMaker install]
时,若是 removeExisting
判断为 true
,会将已安装的约束所有执行 [constraint uninstall]
卸载;
而 updateConstraints:
和上面代码的惟一区别就是在调用 block
以前增长了一句 constraintMaker.updateExisting = YES
标示。
当 [constraint install]
执行时,会判断 updateExisting
的值, 若是为 true
会接着判断约束和已安装的约束是否类似,(判断是否类似的规则是,两条约束只有 constant
常量值不同,其它诸如 firstItem
secondItem
firstAttribute
secondAttribute
relation
multiplier
priority
必须和以前约束彻底一致,才为类似约束。),若是存在类似约束,则进行约束更新,不然就新增这条约束。所以咱们要十分注意 updateConstraints:
新更新的约束会不会和已有的约束冲突, 例如当咱们以前约束为 make.right.equalTo(self.view).offset(-12);
更新后为 make.right.equalTo(self.view.mas_centerX).offset(-15);
这是两条不类似的约束(secondAttribute
不同),若是更新约束,会形成约束冲突。
make.left.right.top.equalTo(self.view).offset(0)
都作了些什么?make.left
生成并返回 MASViewConstraint
对象,须要注意的是:
该对象已保存了调用 view
(FirstView
) 和 left
(FirstAttribute
)
该对象已被添加到 make
的 constraints
数组内保存
MASViewConstraint.right
生成并返回了 MASCompositeConstraint
对象,须要注意的是:
MASCompositeConstraint
对象保存了包含 left
和 top
的两条 MASViewConstraint
对象make
的 constraints
数组以前保存的 MASViewConstraint
对象被替换为该 MASCompositeConstraint
对象MASCompositeConstraint.top
返回以前的 MASCompositeConstraint
对象,须要注意的是:
MASCompositeConstraint
增长了一条 top
约束MASCompositeConstraint .equalTo(self.view)
返回以前的 MASCompositeConstraint
,
MASCompositeConstraint
保存的几条约束, 为他们设置 layoutRelation
、secondView
和 secondAttribute
equalTo()
参数是 view
类型,secondAttribute
依旧是 nil
,会在最后约束安装时若是判断为 nil
则值初始化为 FirstAttribute
MASCompositeConstraint.offset
无返回值
MASCompositeConstraint
保存的几条约束,为他们设置 layoutConstant
最后约束安装时 执行 [constraintMaker install]
; 就会根据 firstView
FirstAttribute
layoutRelation
secondView
secondAttribute
layoutConstant
来生成原生的约束 NSLayoutConstraint
,并将原生约束添加到 firstView
secondView
最近的公共父视图上生效。
Masonry
是如何作到链式优雅调用的?链式编程思想:简单来讲,是将多个操做(多行代码)经过点号(.)连接在一块儿成为一句代码,使代码可读性好。a(1).b(2).c(3)
链式编程特色:方法的返回值是 block , block 必须有返回值(自己对象),block 参数就是须要操做的值。
那 make.left.right.top.bottom.equalTo(self.view).offset(12)
链式调用的具体过程是什么样的呢?
首先 Masonry
定义了一个 MASConstraint
抽象类 上面全部的方法返回值都是 MASConstraint
类型,而全部的调用者除了第一个为 MASConstraintMake
类型,其它都是 MASConstraint
类型调用。因此前一个方法的返回值正好做为下一个方法的调用者,而调用过的全部方法修改的约束都被 maker
的 constraints
所记录下来。随后在 [constraintMaker install]
; 的时候遍历 constraints
执行 [constraint install]
MASViewConstraint
为何要弱引用一个 MASLayoutConstraint 的实例对象,它又用这个对象作了什么?Masonry
库最后都会生成一个 MASVIewConstraint
对象,Masonry
会根据这个对象生成系统原生 NSLayoutConstraint
约束的建立,然后期可能要对这个原生约束进行一些移除操做。须要记录这个原生约束对象。
MASConstraintMaker
持有一个 constraints
数组, 而 MASViewConstrint
类也有一个用来记录约束的数组,这两个数组都是用来记录生成的约束,那这两个数组有什么区别吗?各自的做用又是什么?MASConstraintMaker
的 数组是记录 本次 Masonry
API
调用生成的约束,最后 make
将这个数组内的约束遍历安装 install
。 数组里存储的是 MASViewConstraint
和 MASCompositeConstraint
对象
而 MASViewConstrint
类的数组,记录的是 Masonry
调用者 View
已经安装了哪些约束,这个数组在后期调用者调用 updateConstraints:
时判断,更新的约束是否已经安装了 ,remakeConstraints:
方法时,须要根据数组将已经安装过的约束移除。数组里存储的都是 MASViewConstrint
对象。
尽管笔者水平有限,但对这些问题的拙劣看法仍是奉上,但愿能够给读 Masonry
源码的小伙伴带来些不同的视角,若是对于文中有解读不当的地方也请您不吝指出。