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 来整合基础类型,作为一种参数方便的使用。
使用了继承来分模块来拓展支持属性,并且利用返回自己来实现链式调用。
合理的控制对象类型的顺序来实现对象的控制语法和语义。