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

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

簡單用法

    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)鍵所在,接下來繼續(xù)研究上一篇探索過的ConstraintMaker。

ConstraintMaker

這可以說是整個SnapKit中最關(guān)鍵的類,它將ConstraintViewConstraint聯(lián)系到了一起,是將約束附加到控件上的工具。上篇文章中說道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到底是怎么實現(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)建了一個ConstraintDescription,然后將其作為參數(shù),創(chuàng)建ConstraintMakerExtendable并返回。

ConstraintMakerExtendable

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

這個類并沒有構(gòu)造函數(shù),而是千篇一律的類似sizelayout屬性,可以自行腦補,將size換成其他諸如width、centerX等等眾多屬性,簡單來說這個類的作用還是甩鍋。

雖然沒有找到構(gòu)造函數(shù),但是return self說明make.size已經(jīng)將這個ConstraintMakerExtendable返回了,緊接著調(diào)用equalTo方法。因為這個類即沒有這個方法,也沒有我們想要的構(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
    }
}

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

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,只不過傳入的枚舉類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)對象,再通過對應(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主要是對傳入other: ConstraintRelatableTarget進行轉(zhuǎn)型判斷,根據(jù)不同結(jié)果對related: ConstraintItemconstant: ConstraintConstantTarget進行賦值,最終構(gòu)造并返回一個ConstraintMakerEditable對象。

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í)行完畢了。這些方法只不過是更深入的操作,例如offset設(shè)置偏移量,設(shè)置約束優(yōu)先級等等。

ConstraintDescription

精簡代碼如下:

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時,就利用這些成員變量構(gòu)造一個新的Constraint并返回。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容