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擴(kuò)展了snp屬性,snp為ConstraintViewDSL結(jié)構(gòu)體

public extension ConstraintView {
    var snp: ConstraintViewDSL {
        return ConstraintViewDSL(view: self)
    }
}

ConstraintViewDSL

在ConstraintViewDSL中提供了prepareConstraints、makeConstraints等我們經(jīng)常調(diào)用的方法。

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協(xié)議,ConstraintAttributesDSL主要是增加了iOS 8.0和OSX 10.11之后的新的屬性;

(2)ConstraintAttributesDSL遵循ConstraintBasicAttributesDSL協(xié)議,ConstraintBasicAttributesDSL主要是一些如left、top、right、size等基礎(chǔ)的布局屬性。

(3)通過internal init(view: ConstraintView)方法將要設(shè)置約束的view賦值給self.view

2. 分析設(shè)置約束的過程

通過分析ConstraintViewDSL的makeConstraints方法,了解設(shè)置約束的過程

public func makeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
  ConstraintMaker.makeConstraints(item: self.view, closure: closure)
}

這里通過調(diào)用ConstraintMaker的makeConstraints來實現(xiàn),通過prepareConstraints構(gòu)造Constraint后,進(jìn)行逐個添加和激活

  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
    }
    

擴(kuò)充屬性

(1)ConstraintMaker:就是我們常寫的makeConstraints回調(diào)中make的類型。

    LayoutConstraintItem:是遵循AnyObject的一個協(xié)議,擴(kuò)展了prepare、superview、constraints、add、remove、constraintsSet屬性和方法

    因為ConstraintView擴(kuò)展了這個協(xié)議,所以可以直接傳ConstraintView類型

(2)ConstraintMaker 包含left、top、centerX等基本屬性,且返回ConstraintMakerExtendable,使得其能鏈?zhǔn)秸{(diào)用

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協(xié)議。

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

擴(kuò)充值

(3)ConstraintMakerExtendable遵循ConstraintMakerRelatable協(xié)議,擴(kuò)充了equalTo、equalToSuperview、lessThanOrEqualTo、greaterThanOrEqualTo等方法。
這些方法最終都會調(diào)用ConstraintMakerRelatable的relatedTo方法,將約束描述補(bǔ)充,并返回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
    }

擴(kuò)充計算

ConstraintMakerEditable類型包含multipliedBy、offset、dividedBy、inset等方法,支持對值做相應(yīng)計算。

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

擴(kuò)充優(yōu)先級

ConstraintMakerPrioritizable包含了優(yōu)先級相關(guān)的方法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方法,根據(jù)需要對屬性、值、計算和優(yōu)先級做一系列處理后,我們可以得到通過closure(maker)使maker.descriptions包含所有的約束描述,將每條描述再轉(zhuǎn)換成Constraint類型(真實需要的約束)的約束信息,并返回[Constraint]類型

(2)對[Constraint]的每個Constraint執(zhí)行 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(也就是對應(yīng)的ConstraintView)通過internal func add(constraints: [Constraint]) 方法將LayoutConstraintItem的constraintsSet添加上所有約束

其中constraintsSet是與LayoutConstraintItem相關(guān)聯(lián)的

   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完成了約束的添加約束與對象關(guān)聯(lián),以方便對約束的更新。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • SnapKit是基于NSLayoutConstraint封裝的一個輕量級的布局框架.區(qū)別于iOS9.0中蘋果引入的...
    五月飛閱讀 1,461評論 0 1
  • 前言 SnapKit庫的使用非常簡單,它受到很多開發(fā)者的喜愛和使用。那么,我們知道了怎么使用它后,有沒有想過它的源...
    langkee閱讀 3,231評論 0 13
  • Snapkit是一個AutoLayout的封裝庫,是Masonary在Swift中的代替品。通過SnapKit,我...
    milawoai閱讀 1,266評論 0 2
  • SnapKit 是一個使用 Swift 編寫而來的 AutoLayout 框架, 通過使用 Snapkit, 我們...
    yww閱讀 1,835評論 2 4
  • 前言 這是對 Swift 布局框架 SnapKit 的源碼的一點(diǎn)分析,嘗試搞清,一個好的布局框架,背后都做了些什么...
    KyXu閱讀 3,303評論 3 12

友情鏈接更多精彩內(nèi)容