iOS SnapKit源碼解析(二)進(jìn)入閉包

在上一篇 iOS SnapKit源碼解析(一)makeConstraints的過(guò)程
中,簡(jiǎn)單介紹了snp.makeConstraints的調(diào)用棧稚字,沒有描述閉包之內(nèi)代碼的運(yùn)行過(guò)程厦酬,這篇文章就探究一下閉包之內(nèi)發(fā)生了什么。

簡(jiǎn)單用法

    button.snp.makeConstraints { (make) in
        make.size.equalTo(CGSize(width: 100, height: 100))
        make.center.equalTo(bgView)
    }

    label.snp.makeConstraints { (make) in
        make.top.equalTo(button.snp.bottom).offset(30) 
        make.size.equalTo(CGSize(width: 200, height: 50))
        make.centerX.equalTo(button)
    }

可以看到仗阅,make是閉包的關(guān)鍵所在,接下來(lái)繼續(xù)研究上一篇探索過(guò)的ConstraintMaker减噪。

ConstraintMaker

這可以說(shuō)是整個(gè)SnapKit中最關(guān)鍵的類,它將ConstraintViewConstraint聯(lián)系到了一起醋闭,是將約束附加到控件上的工具朝卒。上篇文章中說(shuō)道ConstraintMakerprepareConstraints方法中閉包的執(zhí)行就避開不談了,本文將從這里入手:

    internal static func prepareConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) -> [Constraint] {
        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)
        }
        return constraints
    }

closure(maker)執(zhí)行了閉包中的代碼抗斤,例如:

make.size.equalTo(CGSize(width: 100, height: 100))

這里的make.size到底是怎么實(shí)現(xiàn)的呢?

不難發(fā)現(xiàn)makeConstraintMaker)有很多成員變量:

public class ConstraintMaker {
    
    public var left: ConstraintMakerExtendable {
        return self.makeExtendableWithAttributes(.left)
    }
    
    public var size: ConstraintMakerExtendable {
        return self.makeExtendableWithAttributes(.size)
    }

    // ......
}

可以看到這些ConstraintMakerExtendable變量都是甩鍋俠龙宏,都交給了ConstraintMakermakeExtendableWithAttributes方法處理:

    internal func makeExtendableWithAttributes(_ attributes: ConstraintAttributes) -> ConstraintMakerExtendable {
        let description = ConstraintDescription(item: self.item, attributes: attributes)
        self.descriptions.append(description)
        return ConstraintMakerExtendable(description)
    }

首先創(chuàng)建了一個(gè)ConstraintDescription负拟,然后將其作為參數(shù),創(chuàng)建ConstraintMakerExtendable并返回掩浙。

ConstraintMakerExtendable

public class ConstraintMakerExtendable: ConstraintMakerRelatable {
    // ......
    public var size: ConstraintMakerExtendable {
        self.description.attributes += .size
        return self
    }
}

這個(gè)類并沒有構(gòu)造函數(shù),而是千篇一律的類似sizelayout屬性厨姚,可以自行腦補(bǔ),將size換成其他諸如width今布、centerX等等眾多屬性经备,簡(jiǎn)單來(lái)說(shuō)這個(gè)類的作用還是甩鍋部默。

雖然沒有找到構(gòu)造函數(shù),但是return self說(shuō)明make.size已經(jīng)將這個(gè)ConstraintMakerExtendable返回了傅蹂,緊接著調(diào)用equalTo方法。因?yàn)檫@個(gè)類即沒有這個(gè)方法犁功,也沒有我們想要的構(gòu)造函數(shù),于是去父類看看浸卦。

ConstraintMakerRelatable

public class ConstraintMakerRelatable {
    
    internal let description: ConstraintDescription
    
    internal init(_ description: ConstraintDescription) {
        self.description = description
    }

    @discardableResult
    public func equalTo(_ other: ConstraintRelatableTarget, _ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
        return self.relatedTo(other, relation: .equal, file: file, line: line)
    }

    internal func relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation, file: String, line: UInt) -> ConstraintMakerEditable {
        // 省略代碼
        let editable = ConstraintMakerEditable(self.description)
        editable.description.sourceLocation = (file, line)
        editable.description.relation = relation
        editable.description.related = related
        editable.description.constant = constant
        return editable
    }
}

到這里算是會(huì)師了案糙,不僅找到了接受ConstraintDescription為參數(shù)的構(gòu)造函數(shù),而且equalTo也在這里时捌。這里equalTo@discardableResult值得注意,說(shuō)明有兩種情況,一種是像我們上面的例子一樣端礼,最多只執(zhí)行到equalTo,忽略返回值蛤奥;另一種就是可以在返回值上進(jìn)行更進(jìn)階的操作。

internal enum ConstraintRelation : Int {
    case equal = 1
    case lessThanOrEqual
    case greaterThanOrEqual
    
    internal var layoutRelation: LayoutRelation {
        get {
            switch(self) {
            case .equal:
                return .equal
            case .lessThanOrEqual:
                return .lessThanOrEqual
            case .greaterThanOrEqual:
                return .greaterThanOrEqual
            }
        }
    }
}

不管是equalTo蟀伸,還是lessThanOrEqualTo或者greaterThanOrEqual,最終都是調(diào)用relatedTo缅刽,只不過(guò)傳入的枚舉類ConstraintRelation不同而已啊掏。

    @discardableResult
    public func equalToSuperview(_ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
        guard let other = self.description.item.superview else {
            fatalError("Expected superview but found nil when attempting make constraint `equalToSuperview`.")
        }
        return self.relatedTo(other, relation: .equal, file: file, line: line)
    }

而像equalToSuperview這種涉及到Superview的關(guān)系,都是在方法內(nèi)部先獲取到Superview衰猛,然后將其作為約束的目標(biāo)對(duì)象迟蜜,再通過(guò)對(duì)應(yīng)的equalTo返回結(jié)果。

    internal func relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation, file: String, line: UInt) -> ConstraintMakerEditable {
        let related: ConstraintItem
        let constant: ConstraintConstantTarget
        
        if let other = other as? ConstraintItem {
            guard other.attributes == ConstraintAttributes.none ||
                  other.attributes.layoutAttributes.count <= 1 ||
                  other.attributes.layoutAttributes == self.description.attributes.layoutAttributes ||
                  other.attributes == .edges && self.description.attributes == .margins ||
                  other.attributes == .margins && self.description.attributes == .edges else {
                fatalError("Cannot constraint to multiple non identical attributes. (\(file), \(line))");
            }
            
            related = other
            constant = 0.0
        } else if let other = other as? ConstraintView {
            related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
            constant = 0.0
        } else if let other = other as? ConstraintConstantTarget {
            related = ConstraintItem(target: nil, attributes: ConstraintAttributes.none)
            constant = other
        } else if #available(iOS 9.0, OSX 10.11, *), let other = other as? ConstraintLayoutGuide {
            related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
            constant = 0.0
        } else {
            fatalError("Invalid constraint. (\(file), \(line))")
        }
        
        let editable = ConstraintMakerEditable(self.description)
        editable.description.sourceLocation = (file, line)
        editable.description.relation = relation
        editable.description.related = related
        editable.description.constant = constant
        return editable
    }

relatedTo主要是對(duì)傳入other: ConstraintRelatableTarget進(jìn)行轉(zhuǎn)型判斷啡省,根據(jù)不同結(jié)果對(duì)related: ConstraintItemconstant: ConstraintConstantTarget進(jìn)行賦值娜睛,最終構(gòu)造并返回一個(gè)ConstraintMakerEditable對(duì)象髓霞。

ConstraintMakerEditable

public class ConstraintMakerEditable: ConstraintMakerPriortizable {

    @discardableResult
    public func multipliedBy(_ amount: ConstraintMultiplierTarget) -> ConstraintMakerEditable {
        self.description.multiplier = amount
        return self
    }
    
    @discardableResult
    public func dividedBy(_ amount: ConstraintMultiplierTarget) -> ConstraintMakerEditable {
        return self.multipliedBy(1.0 / amount.constraintMultiplierTargetValue)
    }
    
    @discardableResult
    public func offset(_ amount: ConstraintOffsetTarget) -> ConstraintMakerEditable {
        self.description.constant = amount.constraintOffsetTargetValue
        return self
    }
    
    @discardableResult
    public func inset(_ amount: ConstraintInsetTarget) -> ConstraintMakerEditable {
        self.description.constant = amount.constraintInsetTargetValue
        return self
    }
    
}

在這里又見到了@discardableResult,如果按照最初的例子make.size.equalTo()畦戒,到這里已經(jīng)執(zhí)行完畢了方库。這些方法只不過(guò)是更深入的操作,例如offset設(shè)置偏移量障斋,設(shè)置約束優(yōu)先級(jí)等等纵潦。

ConstraintDescription

精簡(jiǎn)代碼如下:

public class ConstraintDescription {
    internal var attributes: ConstraintAttributes
    // 省略很多成員變量  
    internal lazy var constraint: Constraint? = {   
        return Constraint(
            // 利用那些成員變量構(gòu)造Constraint
        )
    }()
    
    // MARK: Initialization
    
    internal init(item: LayoutConstraintItem, attributes: ConstraintAttributes) {
        self.item = item
        self.attributes = attributes
    }
    
}

ConstraintDescription有很多成員變量,其中包括我們需要的attributesconstraint配喳,需要返回constraint時(shí),就利用這些成員變量構(gòu)造一個(gè)新的Constraint并返回晴裹。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末被济,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子涧团,更是在濱河造成了極大的恐慌只磷,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泌绣,死亡現(xiàn)場(chǎng)離奇詭異钮追,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)阿迈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門元媚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人苗沧,你說(shuō)我怎么就攤上這事刊棕。” “怎么了待逞?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵甥角,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我识樱,道長(zhǎng)嗤无,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任怜庸,我火速辦了婚禮当犯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘休雌。我一直安慰自己灶壶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布杈曲。 她就那樣靜靜地躺著驰凛,像睡著了一般胸懈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恰响,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天趣钱,我揣著相機(jī)與錄音,去河邊找鬼胚宦。 笑死首有,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的枢劝。 我是一名探鬼主播井联,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼您旁!你這毒婦竟也來(lái)了烙常?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鹤盒,失蹤者是張志新(化名)和其女友劉穎蚕脏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體侦锯,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡驼鞭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尺碰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挣棕。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亲桥,靈堂內(nèi)的尸體忽然破棺而出穴张,到底是詐尸還是另有隱情,我是刑警寧澤两曼,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站玻驻,受9級(jí)特大地震影響悼凑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜璧瞬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一户辫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嗤锉,春花似錦渔欢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)苫幢。三九已至,卻和暖如春垫挨,著一層夾襖步出監(jiān)牢的瞬間韩肝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工九榔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哀峻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓哲泊,卻偏偏與公主長(zhǎng)得像剩蟀,于是被迫代替她去往敵國(guó)和親罚勾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子踢关,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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