关于 Masonry 的一些思考(下)
文章目录
- 前言
- 关于 Masonry 思考的解答
- 1. Masonry 都做了些什么?
- 2. 下面代码会发生循环引用吗,为什么?
- 3. MAS_SHORTHAND、MAS_SHORTHAND_GLOBALS 宏是做什么用的?它的效果是如何实现的呢?
- 4. Masonry 的 makeConstraints: 、updateConstraints:、 remakeConstraints: 有什么区别,分别适合那些场景?
- 5. 描述下代码 make.left.right.top.equalTo(self.view).offset(0) 都做了些什么?
- 6. Masonry 是如何做到链式优雅调用的?
- 8.MASConstraintMaker 持有一个 constraints 数组, 而 MASViewConstrint 类也有一个用来记录约束的数组,这两个数组都是用来记录生成的约束,那这两个数组有什么区别吗?各自的作用又是什么?
- 后记
前言
本篇文章是笔者对上篇文章《关于 Masonry 的一些思考》的一些自己的解答,哪里有理解不到位的地方,请尽情拍砖。如果想先看无答案版,请前往上篇文章 《看完 Masonry
源码后的几点思考?》。
图片来自戴铭文章 《读 SnapKit 和 Masonry 自动布局框架源码》
关于 Masonry 思考的解答
1. Masonry
都做了些什么?
Masonry
是一个让开发者用简洁优雅的语法来调用原生 AutoLayout
进行布局的轻量级框架。Masonry
拥有自己的 DSL
布局语言,让我们可以更具象地描述约束的增加与更新,让约束的代码也变得更加简洁易读、容易理解。
DSL 是一种基于特定领域的语言,它使工作更贴近于客户的理解,而不是实现本身,这样有利于开发过程中,所有参与人员使用同一种语言进行交流。简单来说,就是我们只需描述出我们想要什么效果,而毋需涉及底层实现,这无疑降低了工作过程中沟通协调的门槛。
语言过于苍白,让我们 show code:
原生 AutoLayout
实现一个红色 view 布局
|
|
使用 Masonry 进行布局:
|
|
代码甚至可以再精简下:
|
|
以上代码来自 Masonry 的 github 介绍
经过上面的代码比较,Masonry
语法的简洁优雅效果是浅显易见的。代码不仅变得精简,而且阅读成本也基本降到了最低。
2. 下面代码会发生循环引用吗,为什么?
|
|
答: 不会发生循环引用,方法中 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
也确实是这么做的:
|
|
从上面代码中,可以清除地看到,block
参数在 return
之前就被执行,并未被其他对象引用。
更多关于 NS_NOESCAPE 的介绍
额外拓展,很多同学对
block
的循环引用都不太了解:同样是匿名 block 参数,系统动画
|
|
不会造成循环引用,而 MJRefresh
的 header
初始化方法
|
|
为什么会造成循环引用?
MJRefresh
的 headerWithRefreshingBlock:
方法内部,返回的 MJRefreshNormalHeader
对象强引用了这个 block
,而这个返回对象最后又被 self.scrollView.mj_header
强引用了,也就造成了 self -> scrollView -> mj_header -> block -> self
的强引用闭环,因此会造成循环引用。
headerWithRefreshingBlock:
实现代码:
|
|
系统的动画实现方法中,self
并未和这个 block
产生关联,但 block
确实持有了 self
,但笔者猜测 block
对self 并不是强引用,因为如果在这个动画时间内控制器执行 POP
操作,self
会立即被释放掉,也就是说除了导航控制器栈,self
并未被额外的强引用,否则 self 不会被释放。
self 未引用 block (弱)-> self 。因此也不存在循环引用
想一想,当方法中使用匿名
block
、匿名对象作为参数,这些匿名对象是被谁持有?会在什么时候释放呢?欢迎在评论中探讨。
3. MAS_SHORTHAND
、MAS_SHORTHAND_GLOBALS
宏是做什么用的?它的效果是如何实现的呢?
MAS_SHORTHAND
宏可以在调用 Masonry
api
的时候省去 mas_
前缀
Masonry
为 View
定义了 一个 View+MASAdditions
分类。在这个分类中,所有的成员属性和方法都是带有 mas_
前缀的。Masonry 还另外定义了 View+MASShorthandAdditions
分类,在这个分类中所有的所有属性和成员变量都不带 mas_ 前缀。但这个分类被 #ifdef MAS_SHORTHAND #endif
所包裹。 效果如下:
|
|
这样只有定义了 MAS_SHORTHAND
之后这个分类才会被编译,而这个分类内部所有属性的 get
方法、对外的接口方法实现还是调用的带有 mas_
前缀的方法,对于我们开发者来说,只是在 mas_
属性与方法外面包裹上了一层语法糖。
不带有 mas_
前缀方法的实现:
|
|
|
|
而MAS_SHORTHAND_GLOBALS
宏会将 equalTo()
、 greaterThanOrEqualTo()
、 offset()
宏定义为 mas_equalTo()
、 mas_greaterThanOrEqualTo()
、 mas_offset()
。
而带有 mas_
前缀的方法会将括号内的 block
参数从基本数据类型转化为 NSValue
对象类型
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
|
|
4. Masonry
的 makeConstraints:
、updateConstraints:
、 remakeConstraints:
有什么区别,分别适合那些场景?
|
|
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
不一样),如果更新约束,会造成约束冲突。
5. 描述下代码 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
最近的公共父视图上生效。
6. 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]
7.MASViewConstraint
为什么要弱引用一个 MASLayoutConstraint 的实例对象,它又用这个对象做了什么?
Masonry
库最后都会生成一个 MASVIewConstraint
对象,Masonry
会根据这个对象生成系统原生 NSLayoutConstraint
约束的创建,而后期可能要对这个原生约束进行一些移除操作。需要记录这个原生约束对象。
8.MASConstraintMaker
持有一个 constraints
数组, 而 MASViewConstrint
类也有一个用来记录约束的数组,这两个数组都是用来记录生成的约束,那这两个数组有什么区别吗?各自的作用又是什么?
MASConstraintMaker
的 数组是记录 本次 Masonry
API
调用生成的约束,最后 make
将这个数组内的约束遍历安装 install
。
数组里存储的是 MASViewConstraint
和 MASCompositeConstraint
对象
而 MASViewConstrint
类的数组,记录的是 Masonry
调用者 View
已经安装了哪些约束,这个数组在后期调用者调用 updateConstraints:
时判断,更新的约束是否已经安装了 ,remakeConstraints:
方法时,需要根据数组将已经安装过的约束移除。数组里存储的都是 MASViewConstrint
对象。
后记
尽管笔者水平有限,但对这些问题的拙劣见解还是奉上,希望可以给读 Masonry
源码的小伙伴带来些不一样的视角,如果对于文中有解读不当的地方也请您不吝指出。