SnapKit源码分析

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完成了约束的添加约束与对象关联,以方便对约束的更新。

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

推荐阅读更多精彩内容

  • SnapKit是基于NSLayoutConstraint封装的一个轻量级的布局框架.区别于iOS9.0中苹果引入的...
    五月飞阅读 1,293评论 0 1
  • 前言 SnapKit库的使用非常简单,它受到很多开发者的喜爱和使用。那么,我们知道了怎么使用它后,有没有想过它的源...
    langkee阅读 3,129评论 0 13
  • Snapkit是一个AutoLayout的封装库,是Masonary在Swift中的代替品。通过SnapKit,我...
    milawoai阅读 1,111评论 0 2
  • SnapKit 是一个使用 Swift 编写而来的 AutoLayout 框架, 通过使用 Snapkit, 我们...
    yww阅读 1,636评论 2 4
  • 前言 这是对 Swift 布局框架 SnapKit 的源码的一点分析,尝试搞清,一个好的布局框架,背后都做了些什么...
    KyXu阅读 3,033评论 3 12