SnapKit 源码解读(三):Maker

MakerSnapKit 中最核心的概念,所有关于约束的操作都是通过 Maker 来进行管理和操作的。

ConstraintMaker

ConstraintMaker 是施加、更新和重置约束三个功能的核心类,与之对应的也实现了相关的类方法。同时其还声明了很多计算属性,用来配置约束使用。

初始化方法

ConstraintMaker 是通过 LayoutConstraintItem 类型的 item 参数进行初始化的,LayoutConstraintItem 是一个模型类,保存了所有的约束。

private let item: LayoutConstraintItem
    
internal init(item: LayoutConstraintItem) {
    self.item = item
    self.item.prepare()
}

计算属性

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)
}
    
public var trailing: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.trailing)
}
    
public var width: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.width)
}
    
public var height: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.height)
}
  
public var centerX: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.centerX)
}
   
public var centerY: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.centerY)
}
  
@available(*, deprecated:3.0, message:"Use lastBaseline instead")
public var baseline: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.lastBaseline)
}
    
public var lastBaseline: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.lastBaseline)
}
    
@available(iOS 8.0, OSX 10.11, *)
public var firstBaseline: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.firstBaseline)
}
    
@available(iOS 8.0, *)
public var leftMargin: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.leftMargin)
}
    
@available(iOS 8.0, *)
public var rightMargin: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.rightMargin)
}
    
@available(iOS 8.0, *)
public var topMargin: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.topMargin)
}
    
@available(iOS 8.0, *)
public var bottomMargin: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.bottomMargin)
}
    
@available(iOS 8.0, *)
public var leadingMargin: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.leadingMargin)
}
    
@available(iOS 8.0, *)
public var trailingMargin: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.trailingMargin)
}
    
@available(iOS 8.0, *)
public var centerXWithinMargins: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.centerXWithinMargins)
}
    
@available(iOS 8.0, *)
public var centerYWithinMargins: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.centerYWithinMargins)
}
    
public var edges: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.edges)
}
public var size: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.size)
}
public var center: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.center)
}
    
@available(iOS 8.0, *)
public var margins: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.margins)
}
    
@available(iOS 8.0, *)
public var centerWithinMargins: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.centerWithinMargins)
}

所有的这些计算属性都会通过内部的一个 makeExtendableWithAttributes 方法初始化一个 ConstraintMakerExtendable 类型的对象,并返回之,makeExtendableWithAttributes 方法如下所示:

private var descriptions = [ConstraintDescription]()

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

该方法会根据 self.item 初始化一个 ConstraintDescription 对象,并将该对象存储在 descriptions 数组中,再创造一个 ConstraintMakerExtendable 类型的对象并返回之。

prepareConstraints

prepareConstraints 方法会先生成一个 ConstraintMaker 类型的对象 maker,然后将 maker 通过 closure 传递给外界做一些属性的配置,接着遍历 maker.descriptions 取出其 constraint 属性,并返回之。

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
}

makeConstraints

makeConstraints 方法与 prepareConstraints 方法大同小异,区别在于,makeConstraints 方法的最后不是 return constraints,而是挨个 constraint.activateIfNeeded(updatingExisting: false)

internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
    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)
    }
    for constraint in constraints {
        constraint.activateIfNeeded(updatingExisting: false)
    }
}

remakeConstraints

移除约束的操作分为两步,第一步:移除现有约束,第二步:重新添加约束。

internal static func remakeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
    self.removeConstraints(item: item)
    self.makeConstraints(item: item, closure: closure)
}

移除约束操作就是从 itemconstraints 属性里所有 constraint 挨个失效。

internal static func removeConstraints(item: LayoutConstraintItem) {
    let constraints = item.constraints
    for constraint in constraints {
        constraint.deactivateIfNeeded()
    }
}

updateConstraints

更新约束的操作也分为两部分:首先会判断当前是否已经施加过约束,如果没有,则直接转入 makeConstraints 流程。其余操作与 makeConstraints 类似,区别就是 updatingExisting 参数为 true

internal static func updateConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
    guard item.constraints.count > 0 else {
        self.makeConstraints(item: item, closure: closure)
        return
    }
        
    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)
    }
    for constraint in constraints {
        constraint.activateIfNeeded(updatingExisting: true)
    }
}

ConstraintMakerFinalizable

ConstraintMakerFinalizable 是对 ConstraintDescription 的一层包装,并提供了一些方便方法供我们设置和获取一些值。

public class ConstraintMakerFinalizable {
    
    internal let description: ConstraintDescription
    
    internal init(_ description: ConstraintDescription) {
        self.description = description
    }
    
    @discardableResult
    public func labeled(_ label: String) -> ConstraintMakerFinalizable {
        self.description.label = label
        return self
    }
    
    public var constraint: Constraint {
        return self.description.constraint!
    }
    
}

其中,labeled 方法的最后 return self,是为了可以实现链式调用。

ConstraintMakerPriortizable

ConstraintMakerPriortizableConstraintMakerFinalizable 的子类,扩充了关于优先级的一些方法。

public class ConstraintMakerPriortizable: ConstraintMakerFinalizable {
    
    @discardableResult
    public func priority(_ amount: ConstraintPriority) -> ConstraintMakerFinalizable {
        self.description.priority = amount.value
        return self
    }
    
    @discardableResult
    public func priority(_ amount: ConstraintPriorityTarget) -> ConstraintMakerFinalizable {
        self.description.priority = amount
        return self
    }

}

ConstraintMakerEditable

ConstraintMakerEditableConstraintMakerPriortizable 的子类,扩充了关于设置 multiplierconstant 的方法。

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
    }
    
}

ConstraintMakerRelatable

ConstraintMakerRelatable 是对 ConstraintDescription 的一层包装,作用是为 ConstraintDescription 设置 relation 提供方便方法。有 equalToequalToSuperviewlessThanOrEqualTolessThanOrEqualToSuperviewgreaterThanOrEqualTogreaterThanOrEqualToSuperview 等一系列方法:

@discardableResult
public func equalTo(_ other: ConstraintRelatableTarget, _ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
    return self.relatedTo(other, relation: .equal, file: file, line: line)
}
    
@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)
}
    
@discardableResult
public func lessThanOrEqualTo(_ other: ConstraintRelatableTarget, _ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
    return self.relatedTo(other, relation: .lessThanOrEqual, file: file, line: line)
}
    
@discardableResult
public func lessThanOrEqualToSuperview(_ 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 `lessThanOrEqualToSuperview`.")
    }
    return self.relatedTo(other, relation: .lessThanOrEqual, file: file, line: line)
}
    
@discardableResult
public func greaterThanOrEqualTo(_ other: ConstraintRelatableTarget, _ file: String = #file, line: UInt = #line) -> ConstraintMakerEditable {
    return self.relatedTo(other, relation: .greaterThanOrEqual, file: file, line: line)
}
    
@discardableResult
public func greaterThanOrEqualToSuperview(_ 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 `greaterThanOrEqualToSuperview`.")
    }
    return self.relatedTo(other, relation: .greaterThanOrEqual, file: file, line: line)
}

其中有两个点值得学习。

#file 和 #line

Swift 为我们提供了设置方法参数默认值的方法,就是在方法声明处直接为参数赋值:

..._ file: String = #file, _ line: UInt = #line...

#file#line 则是系统为我们提供的编译符号,供我们获取文件名和行号,更多细节可以参考这篇博客:LOG 输出

fatalError()

有的时候,该崩还是得崩,fatalError() 助你一崩到底,还能添加附加信息,哈啤。

除了以上两点,所有这些方法均调用了内部的 relatedTo 方法:

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
}

该方法会对约束信息的合法性进行一些判断,进而生成一个 ConstraintMakerEditable 类型的对象并返回之。

ConstraintMakerExtendable

ConstraintMakerExtendableConstraintMakerRelatable 的父类,封装了一系列的计算属性,并有 side-effect 每次调用时对 description.attributes 的会有一些额外效果:

public class ConstraintMakerExtendable: ConstraintMakerRelatable {
    
    public var left: ConstraintMakerExtendable {
        self.description.attributes += .left
        return self
    }
    
    public var top: ConstraintMakerExtendable {
        self.description.attributes += .top
        return self
    }
    
    public var bottom: ConstraintMakerExtendable {
        self.description.attributes += .bottom
        return self
    }
    
    public var right: ConstraintMakerExtendable {
        self.description.attributes += .right
        return self
    }
    
    public var leading: ConstraintMakerExtendable {
        self.description.attributes += .leading
        return self
    }
    
    public var trailing: ConstraintMakerExtendable {
        self.description.attributes += .trailing
        return self
    }
    
    public var width: ConstraintMakerExtendable {
        self.description.attributes += .width
        return self
    }
    
    public var height: ConstraintMakerExtendable {
        self.description.attributes += .height
        return self
    }
    
    public var centerX: ConstraintMakerExtendable {
        self.description.attributes += .centerX
        return self
    }
    
    public var centerY: ConstraintMakerExtendable {
        self.description.attributes += .centerY
        return self
    }
    
    @available(*, deprecated:3.0, message:"Use lastBaseline instead")
    public var baseline: ConstraintMakerExtendable {
        self.description.attributes += .lastBaseline
        return self
    }
    
    public var lastBaseline: ConstraintMakerExtendable {
        self.description.attributes += .lastBaseline
        return self
    }
    
    @available(iOS 8.0, OSX 10.11, *)
    public var firstBaseline: ConstraintMakerExtendable {
        self.description.attributes += .firstBaseline
        return self
    }
    
    @available(iOS 8.0, *)
    public var leftMargin: ConstraintMakerExtendable {
        self.description.attributes += .leftMargin
        return self
    }
    
    @available(iOS 8.0, *)
    public var rightMargin: ConstraintMakerExtendable {
        self.description.attributes += .rightMargin
        return self
    }
    
    @available(iOS 8.0, *)
    public var topMargin: ConstraintMakerExtendable {
        self.description.attributes += .topMargin
        return self
    }
    
    @available(iOS 8.0, *)
    public var bottomMargin: ConstraintMakerExtendable {
        self.description.attributes += .bottomMargin
        return self
    }
    
    @available(iOS 8.0, *)
    public var leadingMargin: ConstraintMakerExtendable {
        self.description.attributes += .leadingMargin
        return self
    }
    
    @available(iOS 8.0, *)
    public var trailingMargin: ConstraintMakerExtendable {
        self.description.attributes += .trailingMargin
        return self
    }
    
    @available(iOS 8.0, *)
    public var centerXWithinMargins: ConstraintMakerExtendable {
        self.description.attributes += .centerXWithinMargins
        return self
    }
    
    @available(iOS 8.0, *)
    public var centerYWithinMargins: ConstraintMakerExtendable {
        self.description.attributes += .centerYWithinMargins
        return self
    }
    
    public var edges: ConstraintMakerExtendable {
        self.description.attributes += .edges
        return self
    }
    public var size: ConstraintMakerExtendable {
        self.description.attributes += .size
        return self
    }
    
    @available(iOS 8.0, *)
    public var margins: ConstraintMakerExtendable {
        self.description.attributes += .margins
        return self
    }
    
    @available(iOS 8.0, *)
    public var centerWithinMargins: ConstraintMakerExtendable {
        self.description.attributes += .centerWithinMargins
        return self
    }
    
}

原文地址:SnapKit 源码解读(三):Maker

如果觉得我写的还不错,请关注我的微博@小橘爷,最新文章即时推送~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容