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…

相關文章
相關標籤/搜索