SnapKit 分析

文章目录
  1. 1. SnapKit 是什么
  2. 2. 为什么要设计 SnapKit
  3. 3. SnapKit 代码结构分析
    1. 3.1. 从调用层次分析
    2. 3.2. model 层数据分析
      1. 3.2.1. ConstraintConstantTarget
      2. 3.2.2. ConstraintRelation
      3. 3.2.3. ConstraintAttributes
      4. 3.2.4. ConstraintDescription
    3. 3.3. 分析 SnapKit 链式封装
      1. 3.3.1. 主要组件结
      2. 3.3.2. 使用函数调用栈分析
    4. 3.4. 代码组件分析

请看 Masonry 源码分析

这篇文章是写给自己看的,初稿

(DSL 其实是 Domain Specific Language 的缩写,中文翻译为领域特定语言)

分析源码前想想怎么看源码:

  1. 目的:这个第三方库,他是为了解决什么问题的?
  2. 技术起源:效果实现的核心技术是什么?
  3. 设计模式:整体结构是什么样的?
  4. 该怎么入手:由外而内逐层分析层次结构?

SnapKit 是什么

SnapKit 源码地址

为什么要设计 SnapKit

为了解决 Apple 布局框架 AutoLayout 繁琐的 API

首先和系统提供接口对比下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
label.translatesAutoresizingMaskIntoConstraints = false
let constraint1 = NSLayoutConstraint.init(item: label,
attribute: .left,
relatedBy: .equal,
toItem: view,
attribute: .left,
multiplier: 1,
constant: 150);
constraint1.isActive = true
... top, width
let constraint4 = NSLayoutConstraint.init(item: label,
attribute: .height,
relatedBy: .equal,
toItem: view,
attribute: .notAnAttribute,
multiplier: 1,
constant: 20);
constraint4.isActive = true

Anchor: 只支持 ios >= 9

1
2
3
4
5
6
7
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 150),
label.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
label.widthAnchor.constraint(equalToConstant: 200),
label.heightAnchor.constraint(equalToConstant: 200),
])

1
2
3
4
5
label.snp.makeConstraints { (make) in
make.left.equalTo(view).offset(150)
make.top.equalToSuperview().offset(200)
make.size.equalTo(CGSize(width: 200, height: 20))
}

P.S. SnapKit 内部会帮你调用 view.translatesAutoresizingMaskIntoConstraints = true

可以看出 SnapKit 接口更加简洁

那么他是如何实现的呢?

  1. 如何处理原生 AutoLayout 繁杂接口
  2. 如何实现链式
  3. 如何封装 NSLayoutConstraint
  4. 如何把多个 NSLayoutConstraint 封装成简单的一句搞定的

SnapKit 代码结构分析

从调用层次分析

像洋葱一样一层一层的看源码

  1. 最外层接口
    根据这段代码分析
1
2
3
4
label.snp.makeConstraints { (make) in
make.left.equalTo(view).offset(150)
make.size.equalTo(CGSize(width: 200, height: 20))
}

ConstraintMaker.swift

1
2
3
4
5
6
7
8
9
10
11
12
public class ConstraintMaker {
public var left: ConstraintMakerExtendable { get }
public var top: ConstraintMakerExtendable { get }
public var bottom: ConstraintMakerExtendable { get }
public var right: ConstraintMakerExtendable { get }
......
internal static func prepareConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) -> [Constraint]
internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void)
internal static func remakeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void)
internal static func updateConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void)
......
}
  1. make: ConstraintMaker
  2. left, top,bottom,right……: ConstraintMakerExtendable
  3. 布局方法:make, remake, update

我想知道,他的链式是怎么实现的,如何封装 autolayout 相关的数据的,目前看到的只是平常使用的接口
还要找到下图的这些信息,目前的源码卡不出来
view formula

  1. 接着往里面看
  • ConstraintMakerExtendable 继承自 ConstraintMakerRelatable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ConstraintMakerExtendable : ConstraintMakerRelatable  {
public var left: ConstraintMakerExtendable {
self.description.attributes += .left
return self
}
public var top: ConstraintMakerExtendable {
self.description.attributes += .top
return self
}
public var bottom: ConstraintMakerExtendable {
self.description.attributes += .bottom
return self
}
public var right: ConstraintMakerExtendable {
self.description.attributes += .right
return self
}
......
  • 问题&猜测:

    • description 是啥?不知道
    • 不过他有 attributes 属性,看样子对应 NSLayoutConstraint.Attribute
    • left, top …… 这些都返回 ConstraintMakerExtendable ,猜测他是链式编程中链的节点对象
  • 继续看 ConstraintMakerRelatable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class ConstraintMakerRelatable {
internal let description: ConstraintDescription
internal init(_ description: ConstraintDescription) {
self.description = description
}

internal func relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation, file: String, line: UInt) -> ConstraintMakerEditable {
let related: ConstraintItem
let constant: ConstraintConstantTarget

if let other = other as? ConstraintItem {
guard other.attributes == ConstraintAttributes.none ||
other.attributes.layoutAttributes.count <= 1 ||
other.attributes.layoutAttributes == self.description.attributes.layoutAttributes ||
other.attributes == .edges && self.description.attributes == .margins ||
other.attributes == .margins && self.description.attributes == .edges ||
other.attributes == .directionalEdges && self.description.attributes == .directionalMargins ||
other.attributes == .directionalMargins && self.description.attributes == .directionalEdges else {
fatalError("Cannot constraint to multiple non identical attributes. (\(file), \(line))");
}

related = other
constant = 0.0
} else if let other = other as? ConstraintView {
related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
constant = 0.0
} else if let other = other as? ConstraintConstantTarget {
related = ConstraintItem(target: nil, attributes: ConstraintAttributes.none)
constant = other
} else if #available(iOS 9.0, OSX 10.11, *), let other = other as? ConstraintLayoutGuide {
related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
constant = 0.0
} else {
fatalError("Invalid constraint. (\(file), \(line))")
}

let editable = ConstraintMakerEditable(self.description)
editable.description.sourceLocation = (file, line)
editable.description.relation = relation
editable.description.related = related
editable.description.constant = constant
return editable
}

@discardableResult
public func equalTo(_ other: ConstraintRelatableTarget, _ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
return self.relatedTo(other, relation: .equal, file: file, line: line)
}
......
}

找到了

  • ConstraintConstantTarget(struct)
  • ConstraintRelation(struct)
  • ConstraintAttributes(struct)
  • ConstraintDescription(class)
  • ConstraintItem(class)
  • ConstraintMakerEditable(class)

model 层数据分析

ConstraintConstantTarget

1
2
3
4
5
6
7
8
9
10
typealias LayoutAttribute = NSLayoutConstraint.Attribute

public protocol ConstraintConstantTarget {}
extension CGPoint: ConstraintConstantTarget {}
extension CGSize: ConstraintConstantTarget {}
extension ConstraintInsets: ConstraintConstantTarget {}
extension NSDirectionalEdgeInsets : ConstraintConstantTarget {}
extension ConstraintConstantTarget {
internal func constraintConstantTargetValueFor(layoutAttribute: LayoutAttribute) -> CGFloat
}

看源码知道 ConstraintConstantTarget 是 autolayout 方程式中的 constant,他是这个 constant 扩展封装

ConstraintRelation

1
2
3
4
5
6
internal enum ConstraintRelation : Int { 
case equal
case lessThanOrEqual
case greaterThanOrEqual
internal var layoutRelation: LayoutRelation { get }
}

ConstraintRelation 是 方程式中的 关系符号

ConstraintAttributes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
internal struct ConstraintAttributes : OptionSet, ExpressibleByIntegerLiteral {
...
internal static var none: ConstraintAttributes { return 0 }
internal static var left: ConstraintAttributes { return 1 }
internal static var top: ConstraintAttributes { return 2 }
internal static var right: ConstraintAttributes { return 4 }
internal static var bottom: ConstraintAttributes { return 8 }
internal static var leading: ConstraintAttributes { return 16 }
internal static var trailing: ConstraintAttributes { return 32 }
internal static var width: ConstraintAttributes { return 64 }
...
internal var layoutAttributes:[LayoutAttribute] {
var attrs = [LayoutAttribute]()
if (self.contains(ConstraintAttributes.left)) {
attrs.append(.left)
}
if (self.contains(ConstraintAttributes.top)) {
attrs.append(.top)
}
....

return attrs
}
}
internal func + (left: ConstraintAttributes, right: ConstraintAttributes) -> ConstraintAttributes {
return left.union(right)
}
internal func +=(left: inout ConstraintAttributes, right: ConstraintAttributes) {
left.formUnion(right)
}
  1. ConstraintAttributes 使用了swift 的选择集(可选枚举)
  2. 重载了运算符
  3. 依然猜测 它对应 NSLayoutConstraint.Attribute
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//ConstraintItem.swift
public final class ConstraintItem {
internal weak var target: AnyObject?
internal let attributes: ConstraintAttributes
internal init(target: AnyObject?, attributes: ConstraintAttributes)
internal var layoutConstraintItem: LayoutConstraintItem? {
return self.target as? LayoutConstraintItem
}
}
public func == (lhs: ConstraintItem, rhs: ConstraintItem) -> Bool

// LayoutConstraintItem.swift
public protocol LayoutConstraintItem: class {}
@available(iOS 9.0, OSX 10.11, *)
extension ConstraintLayoutGuide : LayoutConstraintItem {}
extension ConstraintView : LayoutConstraintItem {}

//
public typealias ConstraintLayoutGuide = UILayoutGuide
public typealias ConstraintView = UIView

nice~
ConstraintItem 就是 view 和 他对应布局属性的元组 (view, attr)

ConstraintDescription

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public class ConstraintDescription {
internal let item: LayoutConstraintItem
internal var attributes: ConstraintAttributes
internal var relation: ConstraintRelation?
internal var sourceLocation: (String, UInt)?
internal var label: String?
internal var related: ConstraintItem?
internal var multiplier: ConstraintMultiplierTarget
internal var constant: ConstraintConstantTarget
internal var priority: ConstraintPriorityTarget
internal lazy var constraint: SnapKit.Constraint? { get set } // main
internal init(item: LayoutConstraintItem, attributes: ConstraintAttributes)
}

ConstraintDescription 包含了 autolayout 布局方程式中所有信息,可是不知道为啥不在这里调用 NSLayoutConstraint 添加属性,非要搞一个 SnapKit.Constraint来做这些事

  • ConstraintMakerEditable 是链式编程技巧里面的往后看

分析 SnapKit 链式封装

主要组件结

  • 文件分组
    snapkit_files

使用函数调用栈分析

SnapKit 主流程

snapKit main flow

SnapKit 链式流程

snapKit chain

maker 创建一个其实节点 ContraintExtentable,他维护自己的 description
maker 保存 descriptions[] 数组

传递 description 来延续链节点

to be continued

代码组件分析

Auto Layout Guide