SnapKit 的那些事
SnapKit 是一款可在 iOS 和 OS X 上轻松实现 Auto Layout 的 DSL , 用 Swift 实现.
与之对应, Masonary 用 Objective-C 实现, 两个框架的作者均是 SnapKit 团队
这里有几点需要注意: (下面说的 AutoLayout 均为官方的布局技术, 不包括第三方的布局框架)
Auto Layout 的由来及使用
Auto Layout 是 Apple 提供的用于布局App页面的技术, 可以通过 Interface Builder
或者 纯代码
使用.
- 通过
StoryBoard / xib
可以轻松设计界面, 拖线布局页面, 适用于业务逻辑不是很复杂的项目. - 通过
纯代码
使用 AutoLayout, 由于Apple的原始布局API比较繁琐, 所以诞生了SnapKit, Masonary 这两种框架.
下面是一个例子, view1是子页面, superView 是其父页面, 我们需要设置 view1 的内边距均为10.
由上面可以明显看出, SnapKit 框架通过包装原生布局 API , 极大的简化了API 的调用.
与 AutoLayout 相对的布局方式是 FrameLayout.
- FrameLayout 的优点是固定精确的坐标, 所以很多不需要计算, 性能上比较好, 缺点是对于复杂的界面布局, 比较繁琐.
- AutoLayout 的优点是对于复杂界面比较容易处理, 但由于需要对控件的 frame 进行更多的计算, 性能较差.
- Auto Layout 其实就是对 Cassowary 算法的一种实现, 在布局时可以指定一系列的约束, 比如宽度, 高度, 边距等. 只有知道每一个 View 的
x
,y
,width
,height
, 才能确定它的frame
, 最终渲染到界面上. 对于每一个约束,Cassowary
的布局算法将其转化为简单的线性等式或不等式, 通过求解来算出 View 的frame
.
DSL 的由来及使用
DSL (Domain Specific Language), 特定领域下的语言, 即为了解决某些特定场景下的任务而专门设计的语言. 与 DSL 相对应的是 GPL (General programming language), 通用编程语言, 比如 C/C++/Objective-C/Swift 等.
DSL 的例子:
- 正则表达式: 通过一些规定好的符号和规则, 使用正则表达式解释器来实现字符串的匹配.
- SQL: 通过 create select insert 等 SQL 语句, 利用底层数据库框架, 实现对数据库进行增删改查等操作.
- HTML&CSS: 通过类似 XML 或者 .{}一样的字符规则, 最终都会被浏览器内核转变成 Dom 树, 最终渲染到 WebView 上.
离开了为某一 DSL 专门开发的语言环境或者代码框架, DSL 是无法运行的.
SnapKit 源码分析
有几点需要说明
- 在 iOS 中 ConstraintView 实际就是 UIView.
- ConstraintViewDSL 在初始化时持有传入的 ConstraintView, 并且通过调用 makeConstraints 设置约束.
- ConstraintViewDSL 继承自 ConstraintAttributesDSL , ConstraintAttributesDSL 中定义的是 iOS8 中出现的属性, 比如 leftMargin, topMargin 等. ConstraintBasicAttributesDSL 中定义的是一开始就有的那些属性.
- ConstraintMaker 是设置约束的核心. 其中 ConstraintDescription 包含有所有约束的相关信息.
- ConstraintAttributes 用来描述约束属性, 继承自 OptionSet, 可以将多个选项组成值(位域). 还继承自 ExpressibleByIntegerLiteral, 可以使用整形数据生成选项的集合.
- ConstraintMakerExtendable 继承自 ConstraintMakerRelatable, 后者定义了 equalTo 等相关方法, 用于设置约束值. 并且 equalTo 返回一个 ConstraintMakerEditable 的实例.
- ConstraintMakerEditable 主要用来设置约束的 offset 和 inset 还有 multipliedBy 和 dividedBy 函数, ConstraintMakerPriortizable 用来设置优先级, ConstraintMakerFinalizable 中包含所有的约束信息.
SnapKit 的源码启示
- 条件编译, 命别名
#if
, #else
, #endif
, 可以用来控制编译流程和内容, os()
可以检测系统平台.
typealias
, 可以为类起别名, 也可以实现跨平台
#if os(iOS) || os(tvOS)
import UIKit
#if swift(>=4.2)
typealias LCLayoutRelation = NSLayoutConstraint.Relation
typealias LCLayoutAttribute = NSLayoutConstraint.Attribute
#else
typealias LCLayoutRelation = NSLayoutRelation
typealias LCLayoutAttribute = NSLayoutAttribute
#endif
typealias LCLayoutPriority = UILayoutPriority
#else
import AppKit
typealias LCLayoutRelation = NSLayoutConstraint.Relation
typealias LCLayoutAttribute = NSLayoutConstraint.Attribute
typealias LCLayoutPriority = NSLayoutConstraint.Priority
#endif
- @available
@available
用于函数、方法、类或协议的前面,表明平台和操作系统适用性.
*
表示全平台, deprecated:3.0
, 表示版本号3.0开始过时, message:""
, 表示消息内容.
@available(*, deprecated:3.0, message:"Use newer snp.* syntax.")
public var snp_left: ConstraintItem { return self.snp.left }
@available(iOS 9.0, *) 表示 iOS 9.0 开始适用
-
#file
和#line
是系统为我们提供的编译符号, 用来当前方法的文件名和行号. 配合fatalError()
和guard
, 可以直接提示错误信息
fatalError("Cannot constraint to multiple non identical attributes. (\(file), \(line))");
- 通过这个协议来扩展下只支持的类型,达到限制类型的功能
public protocol LCConstraintRelatableTarget {
}
extension Int: LCConstraintRelatableTarget {
}
extension UInt: LCConstraintRelatableTarget {
}
-
Constraint
类是NSLayoutConstraint
类的包装类, 其中包含了约束的所有参数, 更新和激活约束的相关方法.ConstraintDescription
类是Constraint
类的包装类, 包含约束的所有信息. -
如果要限制一个类不能被其他类限制, 可以加上关键字
final
. -
ConstraintAttributes 继承自 OptionSet, 可以将多个选项组合成一个值, 而不是类似枚举, 只能选择一个. 自定义运算符, 简化方法调用.
internal func + (left: LCConstraintAttributes,
right: LCConstraintAttributes) -> LCConstraintAttributes {
return left.union(right)
}
internal func +=(left: inout LCConstraintAttributes,
right: LCConstraintAttributes) {
left.formUnion(right)
}
internal func -=(left: inout LCConstraintAttributes,
right: LCConstraintAttributes) {
left.subtract(right)
}
internal func ==(left: LCConstraintAttributes,
right: LCConstraintAttributes) -> Bool {
return left.rawValue == right.rawValue
}
- 通过继承
CustomStringConvertible
, 修改对象的description
属性, 自定义对象的描述. 类似下面这样.
struct Person: CustomStringConvertible {
var name: String?
var description: String {
var desc = "<"
desc += self.name ?? "lili"
desc += ">"
return desc
}
}
参考
从 Auto Layout 的布局算法谈性能
深入理解 Autolayout 与列表性能 -- 背锅的 Cassowary 和偷懒的 CPU
谈谈 DSL 以及 DSL 的应用
动态界面:DSL&布局引擎