【iOS 開發(fā)】SnapKit 是怎樣煉成的

前言

這是對 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 是靠什么來確定了三個東西:

  1. 誰在做約束(ConstraintView)
  2. 怎么做約束(ConstraintMaker)
  3. 約束是什么(ConstraintDescription)
let aView = UIView()
aView.snp.makeConstraints({ make in
    make.width.equalToSuperview().dividedBy(2).priority(100)
})

當(dāng)我們寫下這樣的語句時,先忽略掉 <code>snp</code> 是什么不管俯在,里面設(shè)定 aView 的寬度為它的父視圖的一半的這行約束語句竟秫,執(zhí)行了這樣的邏輯:

  1. ConstraintMaker 提供 <code>makeConstraints</code> 方法來讓我們寫約束的同時,開始維護(hù)了一個 ConstraintDescription 數(shù)組跷乐,叫 <code>descriptions</code>
  2. make 本身是 ConstraintMaker 類型的
  3. 在我們寫下 <code>.width</code> 時肥败,<code>descriptions</code> 數(shù)組第一次加入內(nèi)容(<code>self.description</code>),同時我們用這個內(nèi)容生成了一個 ConstraintMakerRelatable 實(shí)例
  4. 在我們寫下 <code>.equalToSuperview()</code> 時愕提,上一步中的內(nèi)容(<code>self.description</code>)繼續(xù)添加信息馒稍,同時我們用它生成了一個 ConstraintMakerEditable 實(shí)例
  5. 之后的 <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 的兩個作用:

  1. snp 有 left top right bottom edges size 等一大堆屬性,這些屬性的類型是 ConstraintItem撞蚕,這是用于構(gòu)造約束位置關(guān)系的


  2. 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 這個文件吧钻洒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市桶错,隨后出現(xiàn)的幾起案子航唆,更是在濱河造成了極大的恐慌,老刑警劉巖院刁,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糯钙,死亡現(xiàn)場離奇詭異,居然都是意外死亡退腥,警方通過查閱死者的電腦和手機(jī)任岸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狡刘,“玉大人享潜,你說我怎么就攤上這事⌒崾撸” “怎么了剑按?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長澜术。 經(jīng)常有香客問我艺蝴,道長,這世上最難降的妖魔是什么鸟废? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任猜敢,我火速辦了婚禮,結(jié)果婚禮上盒延,老公的妹妹穿的比我還像新娘缩擂。我一直安慰自己,他們只是感情好添寺,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布胯盯。 她就那樣靜靜地躺著,像睡著了一般畦贸。 火紅的嫁衣襯著肌膚如雪陨闹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天薄坏,我揣著相機(jī)與錄音趋厉,去河邊找鬼。 笑死胶坠,一個胖子當(dāng)著我的面吹牛君账,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沈善,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼乡数,長吁一口氣:“原來是場噩夢啊……” “哼椭蹄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起净赴,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤绳矩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后玖翅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翼馆,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年金度,在試婚紗的時候發(fā)現(xiàn)自己被綠了应媚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡猜极,死狀恐怖中姜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情跟伏,我是刑警寧澤丢胚,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站受扳,受9級特大地震影響嗜桌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辞色,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浮定。 院中可真熱鬧相满,春花似錦、人聲如沸桦卒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽方灾。三九已至建蹄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間裕偿,已是汗流浹背洞慎。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嘿棘,地道東北人劲腿。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像鸟妙,于是被迫代替她去往敵國和親焦人。 傳聞我的和親對象是個殘疾皇子挥吵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容