前言
這是對 Swift 布局框架 SnapKit 的源碼的一點(diǎn)分析制跟,嘗試搞清,一個好的布局框架酱虎,背后都做了些什么雨膨。
介紹 SnapKit 中的一些類
ConstraintView
等同于 UIView
ConstraintAttributes
用于構(gòu)造約束關(guān)系的各種元素(上下左右等)
ConstraintDescription
包含了包括 ConstraintAttributes 在內(nèi)的各種與約束有關(guān)的元素,一個 ConstraintDescription 實(shí)例读串,就可以提供與一種約束有關(guān)的所有內(nèi)容聊记。
ConstraintMaker
構(gòu)造約束關(guān)系的起點(diǎn),提供了 <code>makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void)</code> 方法來為程序員提供了描述約束的空間恢暖,也可以通過 left right top bottom centerX centerY 等屬性排监,去生成一個 ConstraintMakerExtendable 實(shí)例(見下面)
ConstraintMakerExtendable(繼承 ConstraintMakerRelatable)
提供 left right top bottom leading trailing edges size margins 等內(nèi)容,用以產(chǎn)生一個 ConstraintMakerRelatable 類型的實(shí)例
ConstraintMakerRelatable
直接用于構(gòu)造約束關(guān)系杰捂,也是常用方法 <code>equalTo(_ other: ConstraintRelatableTarget) -> ConstraintMakerEditable</code> 與 <code>equalToSuperview</code> 的來源舆床。核心方法是 <code>relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation, file: String, line: UInt) -> ConstraintMakerEditable</code>,返回 ConstraintMakerEditable 類型的實(shí)例
ConstraintMakerEditable(繼承 ConstraintMakerPriortizable)
在設(shè)定約束的寬度嫁佳、高度以及偏移的時候挨队,提供相應(yīng)的加減乘除方法,返回 ConstraintMakerPriortizable 類型的實(shí)例
ConstraintMakerPriortizable(繼承 ConstraintMakerFinalizable)
提供方法來設(shè)置約束的 priority脱拼,返回 ConstraintMakerFinalizable 類型的實(shí)例
ConstraintMakerFinalizable
一個只有一個類型為 ConstraintDescription 的屬性的類瞒瘸,正如它的類名,有一個 ConstraintMakerFinalizable 實(shí)例熄浓,就得到了對于一個約束的完整描述情臭。
至此省撑,我們已經(jīng)知道 SnapKit 是靠什么來確定了三個東西:
- 誰在做約束(ConstraintView)
- 怎么做約束(ConstraintMaker)
- 約束是什么(ConstraintDescription)
let aView = UIView()
aView.snp.makeConstraints({ make in
make.width.equalToSuperview().dividedBy(2).priority(100)
})
當(dāng)我們寫下這樣的語句時,先忽略掉 <code>snp</code> 是什么不管俯在,里面設(shè)定 aView 的寬度為它的父視圖的一半的這行約束語句竟秫,執(zhí)行了這樣的邏輯:
- ConstraintMaker 提供 <code>makeConstraints</code> 方法來讓我們寫約束的同時,開始維護(hù)了一個 ConstraintDescription 數(shù)組跷乐,叫 <code>descriptions</code>
- make 本身是 ConstraintMaker 類型的
- 在我們寫下 <code>.width</code> 時肥败,<code>descriptions</code> 數(shù)組第一次加入內(nèi)容(<code>self.description</code>),同時我們用這個內(nèi)容生成了一個 ConstraintMakerRelatable 實(shí)例
- 在我們寫下 <code>.equalToSuperview()</code> 時愕提,上一步中的內(nèi)容(<code>self.description</code>)繼續(xù)添加信息馒稍,同時我們用它生成了一個 ConstraintMakerEditable 實(shí)例
- 之后的 <code>.dividedBy(2).priority(100)</code> 使得之前的 ConstraintMakerEditable 實(shí)例變成了一個 ConstraintMakerFinalizable 實(shí)例,這個實(shí)例的 description 屬性的類型是 ConstraintDescription浅侨,它包含了我們所描述的全部內(nèi)容纽谒。但由于 ConstraintMakerEditable 本身就繼承自 ConstraintMakerFinalizable,所以 <code>.dividedBy(2).priority(100)</code> 這一部分即便不寫如输,這條語句在語法上也已經(jīng)完成鼓黔。
做個總結(jié):到這里我們發(fā)現(xiàn) ConstraintMaker 以及和它相關(guān)的類,構(gòu)造了一套 DSL 來讓我們可以輕松地寫出約束語句不见,而這些語句把信息都放到了一個 ConstraintDescription 實(shí)例(<code>self.description</code>)里面澳化,但我們?nèi)匀徊恢浪侨绾我?UIKit 里面的 NSLayoutConstraint 的形式作用的。
snp 是什么
SnapKit 里面存在這樣一些東西:
<code>public protocol ConstraintDSL {}</code>
<code>public protocol ConstraintBasicAttributesDSL : ConstraintDSL {}</code>
<code>public protocol ConstraintAttributesDSL : ConstraintBasicAttributesDSL {}</code>
<code>public struct ConstraintViewDSL: ConstraintAttributesDSL {}</code>
上面我們知道了 <code>aView</code> 作為一個 UIView稳吮,它同時也就是一個 ConstraintView缎谷,ConstraintView 有一個 snp 的屬性,這給我們提供了入口來通過 SnapKit 給任意的 UIView 或 AppKit 里面的 NSView 通過 <code>.snp</code> 這樣的語法來寫約束盖高。
這個 snp 屬性的類型就是結(jié)構(gòu)體 ConstraintViewDSL
一看就是面向協(xié)議的寫法慎陵,通過一個個的 extension 來給 protocol 添加功能,最后用 struct 實(shí)現(xiàn)出來喻奥,就有了 snp 這個屬性席纽。
let topView = UIView()
let centerView = UIView()
centerView.snp.makeConstraints({ make in
make.top.equalTo(topView.snp.bottom).offset(16)
})
這段代碼展現(xiàn)了 snp 的兩個作用:
-
snp 有 left top right bottom edges size 等一大堆屬性,這些屬性的類型是 ConstraintItem撞蚕,這是用于構(gòu)造約束位置關(guān)系的
-
snp 作為 ConstraintViewDSL润梯,有 <code>prepareConstraints</code> <code>makeConstraints</code> <code>remakeConstraints</code> <code>updateConstraints</code> <code>removeConstraints</code> 等函數(shù),我們最常用的是 <code>makeConstraints</code> 甥厦,傳入一個 closure纺铭,在里面寫約束關(guān)系。這里要注意刀疙,我們使用的 <code>makeConstraints</code> 方法來源于 ConstraintViewDSL舶赔,但真正實(shí)現(xiàn)了構(gòu)造約束的其實(shí)是我們上文里面寫的 ConstraintMaker 里面的 <code>makeConstraints</code> 方法,見圖:
約束是如何作用的
到現(xiàn)在我們還是沒說谦秧,從 snp 到 ConstraintMaker竟纳,再到 ConstraintMakerFinalizable 的 description 屬性撵溃,到底哪里創(chuàng)建了 NSLayoutConstraint,答案其實(shí)在之前提過多次的 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)
}
}
我們傳入一個閉包來寫約束關(guān)系時锥累,這個閉包給叫做 maker 的 ConstraintMaker 實(shí)例寫入了信息缘挑,遍歷 maker 的 descriptions 之后(我們之前說一條約束語句最終得到一個 self.description,但往往會有多條約束桶略,所以 ConstraintMakerFinalizable 里面的 self.description语淘,在 ConstraintMaker 里被一個數(shù)組維護(hù)),我們得到了 Constraint 數(shù)組际歼。
Constraint 這個類還沒有介紹過惶翻,不過上面這個核心方法加上以前的內(nèi)容,已經(jīng)可以讓我們猜出來鹅心,約束是怎么寫出來的了:
其他內(nèi)容補(bǔ)充 1
隨便寫了兩句维贺,展示一下各個方法傳入的參數(shù)的類型,發(fā)現(xiàn)有各種 Target巴帮,貌似很復(fù)雜,不過點(diǎn)開之后發(fā)現(xiàn)是這種景象:
說白了就是因?yàn)?<code>equalTo:</code> 這個方法里面能傳的參數(shù)類型比較多虐秋,手動來一個一個限制一下榕茧,我們看到 ConstraintRelatableTarget 這里可以放一些原生的可以代表數(shù)字的類型,外加四個自定義的 Constraint 類型客给。其他的 Target 協(xié)議也差不多是這種情況用押。
個人覺得這種做法還是挺值得學(xué)習(xí)的。
其他內(nèi)容補(bǔ)充 2
SnapKit 里面用來表示位置主體的類其實(shí)不是 ConstraintView靶剑,而是 ConstraintItem
我們管這個“主體”叫 target蜻拨,一個 target,再加上一個 ConstraintAttributes 實(shí)例桩引,就可以組成一個 ConstraintItem缎讼。
有 attributes 屬性很好理解,因?yàn)楸热缥覀內(nèi)プ鰧R坑匠,可以是 aView 的 top 和 bView 的 bottom 對齊血崭,而不能是 aView 和 bView 對齊。但是為什么 target 的類型是 AnyObject 而不是 ConstraintView厘灼,即 UIView 或 NSView 呢夹纫?
在 ConstraintViewDSL 里面,target 確實(shí)是 ConstraintView 類型设凹,
但在 ConstraintLayoutSupportDSL 里面舰讹,target 是 ConstraintLayoutSupport 類型,
在 ConstraintLayoutGuideDSL 里面闪朱,target 是 ConstraintLayoutGuide 類型
這部分就不具體解釋了月匣,想一探究竟的去看 LayoutConstraintItem.swift 這個文件吧钻洒。