在上一篇 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)鍵的類,它將ConstraintView
和Constraint
聯(lián)系到了一起醋闭,是將約束附加到控件上的工具朝卒。上篇文章中說(shuō)道ConstraintMaker
的prepareConstraints
方法中閉包的執(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)make
(ConstraintMaker
)有很多成員變量:
public class ConstraintMaker {
public var left: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.left)
}
public var size: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.size)
}
// ......
}
可以看到這些ConstraintMakerExtendable
變量都是甩鍋俠龙宏,都交給了ConstraintMaker
的makeExtendableWithAttributes
方法處理:
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ù),而是千篇一律的類似size
的layout
屬性厨姚,可以自行腦補(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: ConstraintItem
和constant: 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
有很多成員變量,其中包括我們需要的attributes
和constraint
配喳,需要返回constraint
時(shí),就利用這些成員變量構(gòu)造一個(gè)新的Constraint
并返回晴裹。