SnapKit源码分析
Snapkit版本:5.6.0
1. 给谁做约束
ConstraintView:对iOS而言是UIView,对macOS而言是NSView
#if os(iOS) || os(tvOS)
public typealias ConstraintView = UIView
#else
public typealias ConstraintView = NSView
#endif
给ConstraintView扩展了snp属性,snp为ConstraintViewDSL结构体
public extension ConstraintView {
var snp: ConstraintViewDSL {
return ConstraintViewDSL(view: self)
}
}
ConstraintViewDSL
在ConstraintViewDSL中提供了prepareConstraints、makeConstraints等我们经常调用的方法。
public struct ConstraintViewDSL: ConstraintAttributesDSL {
@discardableResult
public func prepareConstraints(_ closure: (_ make: ConstraintMaker) -> Void) -> [Constraint] {
return ConstraintMaker.prepareConstraints(item: self.view, closure: closure)
}
public func makeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.makeConstraints(item: self.view, closure: closure)
}
public func remakeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.remakeConstraints(item: self.view, closure: closure)
}
public func updateConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.updateConstraints(item: self.view, closure: closure)
}
//....
internal init(view: ConstraintView) {
self.view = view
}
}
(1)ConstraintViewDSL遵循ConstraintAttributesDSL协议,ConstraintAttributesDSL主要是增加了iOS 8.0和OSX 10.11之后的新的属性;
(2)ConstraintAttributesDSL遵循ConstraintBasicAttributesDSL协议,ConstraintBasicAttributesDSL主要是一些如left、top、right、size等基础的布局属性。
(3)通过internal init(view: ConstraintView)方法将要设置约束的view赋值给self.view
2. 分析设置约束的过程
通过分析ConstraintViewDSL的makeConstraints
方法,了解设置约束的过程
public func makeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
ConstraintMaker.makeConstraints(item: self.view, closure: closure)
}
这里通过调用ConstraintMaker的makeConstraints来实现,通过prepareConstraints构造Constraint后,进行逐个添加和激活
internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
let constraints = prepareConstraints(item: item, closure: closure)
for constraint in constraints {
constraint.activateIfNeeded(updatingExisting: false)
}
}
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
}
扩充属性
(1)ConstraintMaker:就是我们常写的makeConstraints回调中make的类型。
LayoutConstraintItem:是遵循AnyObject的一个协议,扩展了prepare、superview、constraints、add、remove、constraintsSet属性和方法
因为ConstraintView扩展了这个协议,所以可以直接传ConstraintView类型
(2)ConstraintMaker 包含left、top、centerX等基本属性,且返回ConstraintMakerExtendable,使得其能链式调用
public class ConstraintMaker {
public var left: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.left)
}
public var top: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.top)
}
public var bottom: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.bottom)
}
public var right: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.right)
}
public var leading: ConstraintMakerExtendable {
return self.makeExtendableWithAttributes(.leading)
}
//...
}
通过ConstraintMaker的makeExtendableWithAttributes方法,不断新增描述中的属性(description.attributes)
其中attributes
遵循OptionSet, ExpressibleByIntegerLiteral协议。
internal func makeExtendableWithAttributes(_ attributes: ConstraintAttributes) -> ConstraintMakerExtendable {
let description = ConstraintDescription(item: self.item, attributes: attributes)
self.descriptions.append(description)
return ConstraintMakerExtendable(description)
}
扩充值
(3)ConstraintMakerExtendable遵循ConstraintMakerRelatable协议,扩充了equalTo、equalToSuperview、lessThanOrEqualTo、greaterThanOrEqualTo等方法。
这些方法最终都会调用ConstraintMakerRelatable的relatedTo
方法,将约束描述补充,并返回ConstraintMakerEditable类型。
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 ||
other.attributes == .directionalEdges && self.description.attributes == .directionalMargins ||
other.attributes == .directionalMargins && self.description.attributes == .directionalEdges 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
}
扩充计算
ConstraintMakerEditable类型包含multipliedBy、offset、dividedBy、inset等方法,支持对值做相应计算。
public class ConstraintMakerEditable: ConstraintMakerPrioritizable {
@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
}
#if os(iOS) || os(tvOS)
@discardableResult
@available(iOS 11.0, tvOS 11.0, *)
public func inset(_ amount: ConstraintDirectionalInsetTarget) -> ConstraintMakerEditable {
self.description.constant = amount.constraintDirectionalInsetTargetValue
return self
}
#endif
}
ConstraintMakerEditable继承自ConstraintMakerPrioritizable
扩充优先级
ConstraintMakerPrioritizable包含了优先级相关的方法priority、priorityRequired、priorityHigh、priorityMedium、priorityLow
ConstraintMakerPrioritizable继承自ConstraintMakerFinalizable
完整描述
ConstraintMakerFinalizable
一个只有一个类型为 ConstraintDescription 的属性的类,正如它的类名,有一个 ConstraintMakerFinalizable 实例,就得到了对于一个约束的完整描述。
过程
blackView.snp.makeConstraints { make in
make.center.equalTo(view)
make.size.equalTo(CGSize(width: 100, height: 100))
}
(1)回到ConstraintMaker的prepareConstraints方法,根据需要对属性、值、计算和优先级做一系列处理后,我们可以得到通过closure(maker)
使maker.descriptions包含所有的约束描述,将每条描述再转换成Constraint类型(真实需要的约束)的约束信息,并返回[Constraint]类型
(2)对[Constraint]的每个Constraint执行 internal func activateIfNeeded(updatingExisting: Bool = false) 方法
通过NSLayoutConstraint的 open class func activate(_ constraints: [NSLayoutConstraint])让每个约束(即Constraint的layoutConstraints属性)激活
LayoutConstraint继承自UIKit.NSLayoutConstraint或者AppKit.NSLayoutConstraint
public final class Constraint {
public var layoutConstraints: [LayoutConstraint]
}
public class LayoutConstraint : NSLayoutConstraint {
public var label: String? {
get {
return self.identifier
}
set {
self.identifier = newValue
}
}
internal weak var constraint: Constraint? = nil
}
/* Convenience method that activates each constraint in the contained array, in the same manner as setting active=YES. This is often more efficient than activating each constraint individually. */
@available(macOS 10.10, *)
open class func activate(_ constraints: [NSLayoutConstraint])
让LayoutConstraintItem(也就是对应的ConstraintView)通过internal func add(constraints: [Constraint]) 方法将LayoutConstraintItem的constraintsSet添加上所有约束
其中constraintsSet是与LayoutConstraintItem相关联的
private var constraintsSet: NSMutableSet {
let constraintsSet: NSMutableSet
if let existing = objc_getAssociatedObject(self, &constraintsKey) as? NSMutableSet {
constraintsSet = existing
} else {
constraintsSet = NSMutableSet()
objc_setAssociatedObject(self, &constraintsKey, constraintsSet, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
return constraintsSet
}
private var constraintsKey: UInt8 = 0
至此,SnapKit完成了约束的添加和约束与对象关联,以方便对约束的更新。