SnapKit 的那些事

SnapKit 是一款可在 iOS 和 OS X 上轻松实现 Auto Layout 的 DSL , 用 Swift 实现.
与之对应, Masonary 用 Objective-C 实现, 两个框架的作者均是 SnapKit 团队

这里有几点需要注意: (下面说的 AutoLayout 均为官方的布局技术, 不包括第三方的布局框架)

Auto Layout 的由来及使用

Auto Layout 是 Apple 提供的用于布局App页面的技术, 可以通过 Interface Builder 或者 纯代码 使用.

  1. 通过 StoryBoard / xib 可以轻松设计界面, 拖线布局页面, 适用于业务逻辑不是很复杂的项目.
  2. 通过 纯代码 使用 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&布局引擎

你可能感兴趣的:(SnapKit 的那些事)