SnapKit
SnapKit 是一个针对 iOS AutoLayout 的 Swift 版的 Domain Specific Language (DSL),它提供了一个 Swift 版的,对 AutoLayout 的良好封装,让 AutoLayout 更加平易近人。
Basic Usage
阅读源码前一定要先了解它的用法,我们才可以去探究它的实现。
以上图为例,View 的顶边底边,左边都是相对 Container 的对应边 20,View的右边距离 Button 的左边 20,高度为 50
用 SnapKit 描述就是
|
|
以上就是基本用法,如此的设计大大减少了我们的代码量。
Get Started
snp
SnapKit 比较优雅的一点就是使用了 snp
来实现非侵入式的拓展。
|
|
源代码中用 snp
这个计算变量用当前 View 初始化了一个 ConstraintViewDSL
结构体,然后所有的操作就跳脱了 View 的拓展,把工作都交给了 ConstraintViewDSL
。
这样的好处是可以避免写更多 View 的拓展,减少了模块对 View 拓展的依赖。
ConstraintViewDSL
首先这个被声明成了结构体而不是类,其次,综管其上下,内部其实只有一个变量,即该 创建该结构体的类。
其中的关键方法
|
|
这里把构建约束的工作交给了 ConstraintMaker
这个我们待会再说。
我们看到使用的时候发现有一个 button.snp.left
的写法,这个的实现放在了 ConstraintViewDSL
遵循的协议 ConstraintAttributesDSL
的继承协议 ConstraintBasicAttributesDSL
的拓展实现中。具体实现如下:
|
|
这个计算属性创建了一个 ConstraintItem
,现在可以理解为一个约束的一部分,描述了一个约束的一部分,比一个对象的 left、right 等等。
ConstraintMaker
我们回到 ConstraintMaker
,核心方法:
|
|
整个流程还是很明确的,但是核心的点就在 closure(maker)
这一句,是通过对 maker
的何种调用来实现这一切的。注意,这里的 item
是调用 snp
的那个对象。
核心就在 maker
的实例方法上。往上翻,就可以发现 maker
的实例方法。举两个简单的如下:
|
|
我们发现这几个计算变量都调用了 makeExtendableWithAttributes()
生成了一个 ConstraintDescription
对象,并返回了一个 ConstraintMakerExtendable
对象。
ConstraintDescription
这是一个类,全部内容如下:
|
|
我们看到在 ConstraintMakerExtendable
中有如下的调用
|
|
这种写法就是 Swift 的 OptionSet
写法,类似于 OC 中的 xxx|yyy
写法,但是 Swift 中可以用 [.xxx, .yyy]
的写法,更优雅,可读性更高。
ConstraintMakerExtendable
ConstraintMakerExtendable
是一个类,继承与 ConstraintMakerRelatable
, 一个可以表示相关性的类。
|
|
然后对其子类 ConstraintMakerExtendable
|
|
则是往 description
中添加东西,然后返回自己以提供链式调用。
所以我们就要关注一下 description
,它做了什么可以储存遮这样的信息,以及他为什么叫 description
Trick
在 equalTo
的地方有一个很有意思的 Trick,它的类型是 ConstraintRelatableTarget
,这样写的目的是我们的 api 也是支持 make.width.height.equalTo(50)
这样的写法。这里的 50 并不是一个 view 或者一个 ConstraintItem
所以需要一个类型将它们统一起来。所以就有了如下的代码:
|
|
这样一个 Trick 让很多不同个类型都统一了起来,成为一个抽象的 ConstraintRelatableTarget
进行使用,如此来支持多种类型。这样就避免为了支持多类型而是用 Any
这个类型不安全的类型,也做到了很好的类型约束,可以在编译期就能检查出类型的错误。
因为在 iOS 9 中引入了一个新的 UILayoutGuide
,它在参与 AutoLayout 布局时与 view 有着同样的作用,所以也出如下的代码
|
|
这里也是将两者合并为了 LayoutConstraintItem
然后我们继续看到 relatedTo()
函数返回了一个 ConstraintMakerEditable
类型。
ConstraintMakerEditable
类似 ConstraintMakerExtendable
, 这个类配置了 description
的 multiplier
offset
inset
dividedBy
属性。
与 ConstraintMakerExtendable
用了同样的技巧来规避内容的调用顺序,即继承,它继承了 ConstraintMakerPriortizable
。
ConstraintMakerPriortizable
这个类配置了 description
的优先级 priority
,然后在用继承,继承了 ConstraintMakerFinalizable
ConstraintMakerFinalizable
这个类配置了 label
值,一个用于对约束的描述,调试使用。
这三个类使用继承,这三个类的属性可以以任意的顺序进行调用,然后 ConstraintMakerEditable
和 ConstraintMakerExtendable
分离,又很好的强调了描述约束的顺序要求。
简单的小结
|
|
(这个约束加的没有意义,只为举个栗子)
``第一个 top
生成了一个 ConstraintMakerExtendable
,之后的 left
则是对 ConstraintMakerExtendable
的调用,向 description
中添加一个新的属性,之后的 equalTo
调用的是其父类的方法。而后返回的才可以配置约束的一些常数部分。语法和语义进行了很好的匹配。
我们之前也探究了 snp.right
的写法,是直接生成了一个 ConstraintItem
,这个是不支持链式调用的。
是 ConstraintMakerExtendable
这个类提供了链式调用的写法来配置 description
。
我们每调用次以 maker
的实例方法都会创建一个 description
,maker
会帮我们记录下来。
再次回到 ConstraintMaker
现在已经配置好了 description
,接下来就在 prepareConstraints()
中提取出所有的 constraint
交还给 makeConstraints()
,然后对所有的 constraint
调用 constraint.activateIfNeeded(updatingExisting: false)
。
Constraint
现在终于来到了 Constraint
类,这个类是 SnapKit 对约束的一个包装,他负责生成真正的 NSLayoutConstraint
的子类 LayoutConstraint
,它提供的功能也不多,只是判断两个约束的相等性以及包装 NSLayoutConstraint.identifier
和持有一个生成它的 Constraint
的弱指针,方便查找。
这个转换在 init()
中就已经完成。这个转换要注意,如果一个对象的 attributes
中如果有多个,比如有 left
以及 right
,它就会生成两个 LayoutConstraint
对象并储存起来。所以如果要提取约束 left
用于之后控制的话,最好不要使用
|
|
常数处理
SnapKit 中使用了很多像上面的 Trick 部分的技巧,包装了很多数值类型,将其拓展不同的类型供不同部分使用,所有的封装如下:
Target | 描述 |
---|---|
ConstraintRelatableTarget | relatedTo 使用 |
ConstraintConstantTarget | offset等使用 |
ConstraintPriorityTarget | priority使用,因为要使用 CGFloat |
ConstraintMultiplierTarget | multiplier 使用,理由同上 |
ConstraintOffsetTarget | offset 使用 |
ConstraintInsetTarget | inset 使用 |
Summary
总结下来,SnapKit 也是用了很多技巧使得这个库使用起来简单而优雅。
首先是使用 ConstraintViewDSL
产生一个对象来跳脱 UIView。
使用了 protocol 来整合基础类型,作为一种参数方便的使用。
使用了继承来分模块来拓展支持属性,并且利用返回自己来实现链式调用。
合理的控制对象类型的顺序来实现对象的控制语法和语义。