SnapKit 是怎样炼成的 | 掘金技术征文

前言

这是对 Swift 布局框架 SnapKit 的源码的一点分析,尝试搞清,一个好的布局框架,背后都作了些什么。javascript

介绍 SnapKit 中的一些类

ConstraintView
等同于 UIViewjava

ConstraintAttributes
用于构造约束关系的各类元素(上下左右等)git

ConstraintDescription
包含了包括 ConstraintAttributes 在内的各类与约束有关的元素,一个 ConstraintDescription 实例,就能够提供与一种约束有关的全部内容。程序员

ConstraintMaker
构造约束关系的起点,提供了 makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) 方法来为程序员提供了描述约束的空间,也能够经过 left right top bottom centerX centerY 等属性,去生成一个 ConstraintMakerExtendable 实例(见下面)github

ConstraintMakerExtendable(继承 ConstraintMakerRelatable)
提供 left right top bottom leading trailing edges size margins 等内容,用以产生一个 ConstraintMakerRelatable 类型的实例swift

ConstraintMakerRelatable
直接用于构造约束关系,也是经常使用方法 equalTo(_ other: ConstraintRelatableTarget) -> ConstraintMakerEditableequalToSuperview 的来源。核心方法是 relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation, file: String, line: UInt) -> ConstraintMakerEditable,返回 ConstraintMakerEditable 类型的实例数组

ConstraintMakerEditable(继承 ConstraintMakerPriortizable)
在设定约束的宽度、高度以及偏移的时候,提供相应的加减乘除方法,返回 ConstraintMakerPriortizable 类型的实例闭包

ConstraintMakerPriortizable(继承 ConstraintMakerFinalizable)
提供方法来设置约束的 priority,返回 ConstraintMakerFinalizable 类型的实例app

ConstraintMakerFinalizable
一个只有一个类型为 ConstraintDescription 的属性的类,正如它的类名,有一个 ConstraintMakerFinalizable 实例,就获得了对于一个约束的完整描述。框架


至此,咱们已经知道 SnapKit 是靠什么来肯定了三个东西:

  1. 谁在作约束(ConstraintView)
  2. 怎么作约束(ConstraintMaker)
  3. 约束是什么(ConstraintDescription)
let aView = UIView()
aView.snp.makeConstraints({ make in
    make.width.equalToSuperview().dividedBy(2).priority(100)
})复制代码

当咱们写下这样的语句时,先忽略掉 snp 是什么无论,里面设定 aView 的宽度为它的父视图的一半的这行约束语句,执行了这样的逻辑:

  1. ConstraintMaker 提供 makeConstraints 方法来让咱们写约束的同时,开始维护了一个 ConstraintDescription 数组,叫 descriptions
  2. make 自己是 ConstraintMaker 类型的
  3. 在咱们写下 .width 时,descriptions 数组第一次加入内容(self.description),同时咱们用这个内容生成了一个 ConstraintMakerRelatable 实例
  4. 在咱们写下 .equalToSuperview() 时,上一步中的内容(self.description)继续添加信息,同时咱们用它生成了一个 ConstraintMakerEditable 实例
  5. 以后的 .dividedBy(2).priority(100) 使得以前的 ConstraintMakerEditable 实例变成了一个 ConstraintMakerFinalizable 实例,这个实例的 description 属性的类型是 ConstraintDescription,它包含了咱们所描述的所有内容。但因为 ConstraintMakerEditable 自己就继承自 ConstraintMakerFinalizable,因此 .dividedBy(2).priority(100) 这一部分即使不写,这条语句在语法上也已经完成。

作个总结:到这里咱们发现 ConstraintMaker 以及和它相关的类,构造了一套 DSL 来让咱们能够轻松地写出约束语句,而这些语句把信息都放到了一个 ConstraintDescription 实例(self.description)里面,但咱们仍然不知道它是如何以 UIKit 里面的 NSLayoutConstraint 的形式做用的。

snp 是什么

SnapKit 里面存在这样一些东西:
public protocol ConstraintDSL {}
public protocol ConstraintBasicAttributesDSL : ConstraintDSL {}
public protocol ConstraintAttributesDSL : ConstraintBasicAttributesDSL {}
public struct ConstraintViewDSL: ConstraintAttributesDSL {}

上面咱们知道了 aView 做为一个 UIView,它同时也就是一个 ConstraintView,ConstraintView 有一个 snp 的属性,这给咱们提供了入口来经过 SnapKit 给任意的 UIView 或 AppKit 里面的 NSView 经过 .snp 这样的语法来写约束。

这个 snp 属性的类型就是结构体 ConstraintViewDSL

一看就是面向协议的写法,经过一个个的 extension 来给 protocol 添加功能,最后用 struct 实现出来,就有了 snp 这个属性。

let topView = UIView()
let centerView = UIView()
centerView.snp.makeConstraints({ make in
    make.top.equalTo(topView.snp.bottom).offset(16)
})复制代码

这段代码展示了 snp 的两个做用:

  1. snp 有 left top right bottom edges size 等一大堆属性,这些属性的类型是 ConstraintItem,这是用于构造约束位置关系的
  2. snp 做为 ConstraintViewDSL,有 prepareConstraints makeConstraints remakeConstraints updateConstraints removeConstraints 等函数,咱们最经常使用的是 makeConstraints ,传入一个 closure,在里面写约束关系。这里要注意,咱们使用的 makeConstraints 方法来源于 ConstraintViewDSL,但真正实现了构造约束的实际上是咱们上文里面写的 ConstraintMaker 里面的 makeConstraints 方法,见图:

约束是如何做用的

到如今咱们仍是没说,从 snp 到 ConstraintMaker,再到 ConstraintMakerFinalizable 的 description 属性,到底哪里建立了 NSLayoutConstraint,答案其实在以前提过屡次的 ConstraintMaker 里面

// public class ConstraintMaker

internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
    let maker = ConstraintMaker(item: item)
    closure(maker)
    var constraints: [Constraint] = []
    for description in maker.descriptions {
        guard let constraint = description.constraint else {
            continue
        }
        constraints.append(constraint)
    }
    for constraint in constraints {
        constraint.activateIfNeeded(updatingExisting: false)
    }
}

internal static func updateConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
    guard item.constraints.count > 0 else {
        self.makeConstraints(item: item, closure: closure)
        return
    }

    let maker = ConstraintMaker(item: item)
    closure(maker)
    var constraints: [Constraint] = []
    for description in maker.descriptions {
        guard let constraint = description.constraint else {
            continue
        }
        constraints.append(constraint)
    }
    for constraint in constraints {
        constraint.activateIfNeeded(updatingExisting: true)
    }
}复制代码

咱们传入一个闭包来写约束关系时,这个闭包给叫作 maker 的 ConstraintMaker 实例写入了信息,遍历 maker 的 descriptions 以后(咱们以前说一条约束语句最终获得一个 self.description,但每每会有多条约束,因此 ConstraintMakerFinalizable 里面的 self.description,在 ConstraintMaker 里被一个数组维护),咱们获得了 Constraint 数组。


Constraint 这个类尚未介绍过,不过上面这个核心方法加上之前的内容,已经可让咱们猜出来,约束是怎么写出来的了:

其余内容补充 1


随便写了两句,展现一下各个方法传入的参数的类型,发现有各类 Target,貌似很复杂,不过点开以后发现是这种景象:

说白了就是由于 equalTo: 这个方法里面能传的参数类型比较多,手动来一个一个限制一下,咱们看到 ConstraintRelatableTarget 这里能够放一些原生的能够表明数字的类型,外加四个自定义的 Constraint 类型。其余的 Target 协议也差很少是这种状况。

我的以为这种作法仍是挺值得学习的。

其余内容补充 2

SnapKit 里面用来表示位置主体的类其实不是 ConstraintView,而是 ConstraintItem
咱们管这个“主体”叫 target,一个 target,再加上一个 ConstraintAttributes 实例,就能够组成一个 ConstraintItem。


有 attributes 属性很好理解,由于好比咱们去作对齐,能够是 aView 的 top 和 bView 的 bottom 对齐,而不能是 aView 和 bView 对齐。可是为何 target 的类型是 AnyObject 而不是 ConstraintView,即 UIView 或 NSView 呢?

在 ConstraintViewDSL 里面,target 确实是 ConstraintView 类型,
但在 ConstraintLayoutSupportDSL 里面,target 是 ConstraintLayoutSupport 类型,
在 ConstraintLayoutGuideDSL 里面,target 是 ConstraintLayoutGuide 类型

这部分就不具体解释了,想一探究竟的去看 LayoutConstraintItem.swift 这个文件吧。

掘金技术征文:gold.xitu.io/post/58522d…

相关文章
相关标签/搜索