SnapKit 是 iOS / macOS 社区最广泛使用的 Auto Layout DSL 库,由 Robert Payne 等人维护,目前在 GitHub 已收获 20k+ star。它在 NSLayoutConstraint 之上构建了一整套链式、类型安全、面向协议的声明式约束语法。本文基于 v5.7.1(2025 年 2 月发布,最后 master commit 2025-05-08 19f59a6)源码进行分析,代码总量不足 3500 行,却用一个分级的 Builder 链路,把 NSLayoutConstraint 那套冗长的 API 封装成了今天我们习惯的 make.left.equalTo(x).offset(10)。
一、整体架构 SnapKit 的核心设计可以用一句话概括:用编译期强约束的链式 Builder,驱动一个延迟构建的 ConstraintDescription,最终在闭包结束后一次性生成 NSLayoutConstraint 并激活。
graph TB subgraph "DSL 入口层" V["UIView / NSView / UILayoutGuide"] SNP["view.snp(ConstraintViewDSL)"] MK["makeConstraintsremakeConstraintsupdateConstraints"] end subgraph "Builder 链(阶段化构建器)" CM["ConstraintMaker(属性起点 make.left)"] CME["ConstraintMakerExtendable(追加属性 .top.right)"] CMR["ConstraintMakerRelatable(关系 equalTo/greaterThan)"] CMED["ConstraintMakerEditable(multipliedBy/offset/inset)"] CMP["ConstraintMakerPrioritizable(priority)"] CMF["ConstraintMakerFinalizable(labeled)"] end subgraph "描述 & 协议层" DESC["ConstraintDescription(懒加载装配)"] TARGETS["Target 协议族Relatable/Constant/OffsetInset/Multiplier/Priority"] ATTR["ConstraintAttributes(OptionSet 位运算)"] end subgraph "约束层" C["Constraint(封装一组 LayoutConstraint)"] LC["LayoutConstraint(NSLayoutConstraint 子类)"] ITEM["LayoutConstraintItem(UIView/UILayoutGuide)"] end subgraph "系统层" NSLC["NSLayoutConstraint.activate"] end V -->|扩展属性| SNP SNP --> MK MK -->|创建| CM CM -->|返回| CME CME -->|继承| CMR CMR -->|equalTo| CMED CMED -->|继承| CMP CMP -->|priority| CMF CM -.写入.-> DESC CME & CMR & CMED & CMP & CMF -.读写.-> DESC DESC -.lazy 构建.-> C C --> LC C -.关联对象.-> ITEM DESC -.使用.-> ATTR CMR -.类型匹配.-> TARGETS C --> NSLC 源码目录(Sources/,约 3500 行) Sources/ ├── ConstraintView.swift # typealias UIView/NSView → ConstraintView ├── ConstraintLayoutGuide.swift # typealias UILayoutGuide/NSLayoutGuide ├── ConstraintLayoutSupport.swift # typealias UILayoutSupport ├── Typealiases.swift # LayoutRelation / LayoutAttribute / LayoutPriority ├── ConstraintConfig.swift # interfaceLayoutDirection 全局开关 │ ├── ConstraintView+Extensions.swift # view.snp 入口(含 snp_ 老别名) ├── ConstraintLayoutGuide+Extensions.swift # guide.snp 入口 ├── UILayoutSupport+Extensions.swift # topLayoutGuide.snp(iOS 11 前) ├── ConstraintViewDSL.swift # makeConstraints / remake / update / remove ├── ConstraintLayoutGuideDSL.swift # LayoutGuide 版 DSL ├── ConstraintLayoutSupportDSL.swift # LayoutSupport 版 DSL ├── ConstraintDSL.swift # DSL 协议 + 属性定义(left/top/edges/...) │ ├── ConstraintMaker.swift # 属性起点(make.left / make.edges / ...) ├── ConstraintMakerExtendable.swift # 可追加属性(.top.right) ├── ConstraintMakerRelatable.swift # equalTo / lessThanOrEqual / greaterThanOrEqual ├── ConstraintMakerRelatable+Extensions.swift # equalToSuperview 闭包版 ├── ConstraintMakerEditable.swift # multipliedBy / dividedBy / offset / inset ├── ConstraintMakerPrioritizable.swift # priority(...) ├── ConstraintMakerFinalizable.swift # labeled(...) + .constraint │ ├── ConstraintAttributes.swift # OptionSet 属性位图 ├── ConstraintRelation.swift # equal / lessThanOrEqual / greaterThanOrEqual ├── ConstraintPriority.swift # required/high/medium/low ├── ConstraintDescription.swift # Builder 中间态 + lazy Constraint ├── ConstraintItem.swift # (target, attributes) 二元组(弱引用) ├── LayoutConstraintItem.swift # UIView/UILayoutGuide 统一协议 ├── ConstraintInsets.swift # UIEdgeInsets typealias ├── ConstraintDirectionalInsets.swift # NSDirectionalEdgeInsets(iOS 11+) │ ├── ConstraintRelatableTarget.swift # 可作为 equalTo 参数的类型标记 ├── ConstraintConstantTarget.swift # constant 目标 + 属性→CGFloat 的派发 ├── ConstraintOffsetTarget.swift # offset 目标 ├── ConstraintInsetTarget.swift # inset 目标(含标量→insets 转换) ├── ConstraintDirectionalInsetTarget.swift # 方向 insets 目标 ├── ConstraintMultiplierTarget.swift # multiplier 目标 ├── ConstraintPriorityTarget.swift # priority 目标(Int/Float/UILayoutPriority) │ ├── Constraint.swift # 真正的约束对象 + activate/deactivate ├── LayoutConstraint.swift # NSLayoutConstraint 子类,回指 Constraint ├── Debugging.swift # LayoutConstraint.description 美化 └── PrivacyInfo.xcprivacy # 隐私清单 核心设计思想 模式 作用 源码体现 阶段化 Builder(Staged Builder / Step Builder) 通过继承链控制链式调用的合法顺序,让 make.left.offset(10).equalTo(...) 这类非法组合在编译期报错 ConstraintMaker → Extendable → Relatable → Editable → Prioritizable → Finalizable 协议 + 类型擦除(POP) 让 Int / Float / CGFloat / CGSize / CGPoint / UIEdgeInsets / UIView / ConstraintItem 等异构类型可以统一作为 equalTo / offset / priority 的参数 Constraint*Target 协议族 OptionSet 位运算 用 32 位整型表达「属性集合」,.edges = [.left, .top, .right, .bottom] 这类聚合用位或实现,避免反复建数组 ConstraintAttributes Associated Object 在不污染 UIView 原类的前提下,为每个 View 绑定「它持有的 SnapKit Constraint 集合」和「snp.label」 LayoutConstraintItem.constraintsSet + ConstraintDSL.setLabel Lazy 求值 ConstraintDescription.constraint 是 lazy var,闭包执行结束前只是收集参数,真正的 Constraint 构造和 NSLayoutConstraint 生成都推迟到最后 ConstraintDescription.constraint 跨平台 typealias 通过一组 typealias 把 iOS / macOS / tvOS 的 UIView/NSView、UIEdgeInsets/NSEdgeInsets、UILayoutPriority/NSLayoutConstraint.Priority 等抹平 ConstraintView.swift / Typealiases.swift 二、DSL 入口:view.snp SnapKit 没有像 Masonry 那样污染 UIView 的命名空间,而是通过一个命名空间结构体暴露 DSL。
...