在上一篇 iOS SnapKit源码解析(一)makeConstraints的过程
中,简单介绍了snp.makeConstraints
的调用栈,没有描述闭包之内代码的运行过程,这篇文章就探究一下闭包之内发生了什么。
简单用法
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
是闭包的关键所在,接下来继续研究上一篇探索过的ConstraintMaker
。
ConstraintMaker
这可以说是整个SnapKit中最关键的类,它将ConstraintView
和Constraint
联系到了一起,是将约束附加到控件上的工具。上篇文章中说道ConstraintMaker
的prepareConstraints
方法中闭包的执行就避开不谈了,本文将从这里入手:
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)
执行了闭包中的代码,例如:
make.size.equalTo(CGSize(width: 100, height: 100))
这里的make.size
到底是怎么实现的呢?
不难发现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)
}
首先创建了一个ConstraintDescription
,然后将其作为参数,创建ConstraintMakerExtendable
并返回。
ConstraintMakerExtendable
public class ConstraintMakerExtendable: ConstraintMakerRelatable {
// ......
public var size: ConstraintMakerExtendable {
self.description.attributes += .size
return self
}
}
这个类并没有构造函数,而是千篇一律的类似size
的layout
属性,可以自行脑补,将size
换成其他诸如width
、centerX
等等众多属性,简单来说这个类的作用还是甩锅。
虽然没有找到构造函数,但是return self
说明make.size
已经将这个ConstraintMakerExtendable
返回了,紧接着调用equalTo
方法。因为这个类即没有这个方法,也没有我们想要的构造函数,于是去父类看看。
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
为参数的构造函数,而且equalTo
也在这里。这里equalTo
的@discardableResult
值得注意,说明有两种情况,一种是像我们上面的例子一样,最多只执行到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
,最终都是调用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
的关系,都是在方法内部先获取到Superview
,然后将其作为约束的目标对象,再通过对应的equalTo
返回结果。
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
进行转型判断,根据不同结果对related: ConstraintItem
和constant: ConstraintConstantTarget
进行赋值,最终构造并返回一个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()
,到这里已经执行完毕了。这些方法只不过是更深入的操作,例如offset
设置偏移量,设置约束优先级等等。
ConstraintDescription
精简代码如下:
public class ConstraintDescription {
internal var attributes: ConstraintAttributes
// 省略很多成员变量
internal lazy var constraint: Constraint? = {
return Constraint(
// 利用那些成员变量构造Constraint
)
}()
// MARK: Initialization
internal init(item: LayoutConstraintItem, attributes: ConstraintAttributes) {
self.item = item
self.attributes = attributes
}
}
ConstraintDescription
有很多成员变量,其中包括我们需要的attributes
和constraint
,需要返回constraint
时,就利用这些成员变量构造一个新的Constraint
并返回。