Swift学习

SnapKit源码解析

2019-07-22  本文已影响0人  milawoai

Snapkit是一个AutoLayout的封装库,是Masonary在Swift中的代替品。通过SnapKit,我们可以方便的进行UI的操作。

众所周知,苹果提供了AutoLayout方便了UI设计,但是官方提供的Api极度蛋疼, 为了一个四边相等,我们需要书写如下数据:

marqueeLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 0).isActive = true
marqueeLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: 0).isActive = true
marqueeLabel.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
marqueeLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true

使用Snapkit之后,就可以改成如下数据

 marqueeLabel.snp.makeConstraints {(make) in
   make.edges.equalToSuperview()
 }

1、snp

ConstraintView+Extensions.swift

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

可以看到snp是ConstraintView扩展中提供的功能, ConstraintView本身是UIView的别名

涉及类:ConstraintView、ConstrainDSL

1.1、ConstraintView

ConstraintView是外观模式的一个标准应用,通过别名,它混一了iOS的UIView与MacOS的NSView。

#if os(iOS) || os(tvOS)
    import UIKit
#else
    import AppKit
#endif

#if os(iOS) || os(tvOS)
    public typealias ConstraintView = UIView
#else
    public typealias ConstraintView = NSView
#endif

继承协议:ConstraintRelatableTarget

1.1.1、ConstraintRelatableTarget

Snapkit库中,对外观模式的应用是十分值得学习的地方。
在设置约束的过程中,会使用多种类型的数据:

make.edges.equalTo(view1.snp.bottom)
make.edges.equalTo(view1)
make.size.equalTo(10)
make.size.equalTo(CGPoint(x: 10, y: 10))

可以看到,equalTo()函数的参数存在许许多多的数据类型,Snapkit通过协议ConstraintRelatableTarget对其进行了方便的封装。

public protocol ConstraintRelatableTarget {
}

extension Int: ConstraintRelatableTarget {
}

......

extension ConstraintInsets: ConstraintRelatableTarget {
}

extension ConstraintItem: ConstraintRelatableTarget {
}

extension ConstraintView: ConstraintRelatableTarget {
}
ps:

UIView在Snapkit中的别名:

结构体:ConstraintView,
协议: LayoutConstraintItem,添加各种约束
包装者:ConstraintItem,将View与Attribute混为一体

2、makeConstraints

 marqueeLabel.snp.~~**makeConstraints**~~

updateConstraints,removeConstraints,makeConstraints 这一系列方法就不展开了。

顾名思义,makeConstraints函数中生成了NSLayout的约束。

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

可以看出,ConstraintViewDSL中的makeConstraints是对ConstraintMaker的makeConstraints进行的封装。

涉及类 ConstraintViewDSL,ConstraintMaker

2.1、ConstraintViewDSL

image.png

本类的初始化需要ConstraintView,也就是marqueeLabel
封装了prepareConstraints,makeConstraints,remakeConstraints,updateConstraints,removeConstraints等 一系列处理方法。它们统一都是调用ConstraintMaker类的静态同名方法,只是将ConstraintView作为第一个参数统一进去。

继承类:ConstraintAttributesDSL,ConstraintBasicAttributesDSL

2.1.1、ConstraintAttributesDSL,ConstraintBasicAttributesDSL

涉及类 ConstraintItem

这两个类可以放在一起说,它们都是提供了生成ConstraintItem的数据。

public var left: ConstraintItem {
   return ConstraintItem(
     target: self.target, 
     attributes: ConstraintAttributes.left
   )
}

可以猜出,这里是和

make.left.equalToSuperview()

相关的,至于如何使用,我们会在生成约束的地方来讲。

2.1.2、ConstraintItem
image.png

ConstraintItem很好理解,就是将目标(target)和约束 (attributes)绑定在一起。

可以看到target是AnyObject,这是因为target不一定是UIView.下面我们讲到Constraint类时,会发现Constraint的from,to属性都是ConstraintItem,target在from中一般是一个UIView,而在to中,由于有make.width.equalTo(100)这种属性存在,ConstraintItem可能没有target。

3、ConstraintMaker

ConstraintMaker 是Snapkit的核心类,在本类中生成了约束

让我们来看一下makeConstraints的定义:(展开版)

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

本类主要分成了生成约束描述与约束描述翻译两个部分:

3.1、生成约束描述

第一部分:约束定义,通过makeExtendableWithAttributes生成一系列ConstraintMakerExtendable 型数据:

关键类 ConstraintDescription
**涉及类 ConstraintAttributes, ConstraintDescription,ConstraintMakerExtendable,ConstraintMakerRelatable **

1. private var descriptions = [ConstraintDescription]()
 
2. public var left: ConstraintMakerExtendable {
        return self.makeExtendableWithAttributes(.left)
    }
    
3. func makeExtendableWithAttributes(_ attributes: ConstraintAttributes) -> ConstraintMakerExtendable {
        let description = ConstraintDescription(item: self.item, attributes: attributes)
        self.descriptions.append(description)
        return ConstraintMakerExtendable(description)
    }
    

由此可知


marqueeLabel.snp.makeConstraints {(make) in
  ~~** make.left.equalToSuperview()**~~
 }
 
{(make:ConstraintMaker) in
    let maker = ConstraintMaker(item: item)
    let description = ConstraintDescription(
        item: marqueeLabel,
        attributes: ConstraintAttributes.left
    )
    self.descriptions.append(description)
    let extend : ConstraintMakerExtendable = 
    ConstraintMakerExtendable(description) 
    
    extend.equalToSuperview()
}

3.1.1 ConstraintAttributes

在此类中,封装了 Snapkit 到 LayoutAttribute 的转换

**继承协议 OptionSet, ExpressibleByIntegerLiteral **

通过这两个协议,使得ConstraintAttributes可以通过 Int,Int数组,枚举,枚举数组等各种形式初始化.可以通过container决定是否存在数据。

在ConstraintAttributes中,提供了一系列left,right之类的静态成员。

static var left: ConstraintAttributes {
    return 1 
}

LayoutAttribute类似ConstraintView,也是通过外观模式混一了iOS,MacOS,多种版本,可以简单的对标成NSLayoutConstraint。

var layoutAttributes:[LayoutAttribute] 就是协议NSLayoutConstraint的数组。


var layoutAttributes:[LayoutAttribute] {
    var attrs = [LayoutAttribute]()
    if (self.contains(ConstraintAttributes.left)) {
            attrs.append(.left)
        }
        if (self.contains(ConstraintAttributes.top)) {
            attrs.append(.top)
        }
}

可以看到,根据自身是否含有静态成员,NSLayoutAttribute存储到了ConstraintAttributes的layoutAttributes属性中。

也就是说


ConstraintAttributes.left 中存在一个

marqueeLabel.leftAnchor

3.1.2 ConstraintDescription

ConstraintDescription 是约束的描述者,
约束的生成过程,就是从
make.top.lessThanOrEqualTo(imageView.snp.bottom).offset(20).multipliedBy(2).priorityLow()
这样的语句转成ConstraintDescription的过程

 let description = ConstraintDescription(
        item: marqueeLabel,
        attributes: ConstraintAttributes.left
    )
self.descriptions.append(description)

可以看出ConstraintDescription通过item与ConstraintAttributes初始化,

来看下的类图

image.png

对其中的各个属性,我们来一一解释:

item: LayoutConstraintItem
attribute: ConstraintAttributes
这两个属性在初始化时写入

relation: ConstraintRelation
一个简单的枚举,对应到 NSLayoutConstraint.Relation 的三种状态

case .equal:
   return .equal
case .lessThanOrEqual:
   return .lessThanOrEqual
case .greaterThanOrEqual:
   return .greaterThanOrEqual

对应了 lessThanOrEqualTo

related: ConstraintItem
ConstraintItem包裹LayoutConstraintItemConstraintAttributes,用来判断是否数据是否一致

对应了 imageView.snp.bottom

multiplier: ConstraintMultiplierTarget
ConstraintMultiplierTarget将Int,UInt,Float,CGFloat等一系列数据转换为CGFloat

对应.multipliedBy(2)

constant: ConstraintConstantTarget
这个需要结合ConstraintInsetTarget : ConstraintConstantTarget来看
ConstraintConstantTarget将Int,UInt,Float,CGFloat, UIEdgeInsets, CGPoint, CGSize 等一系列数据转换为需要的约束值CGFloat:

extension ConstraintConstantTarget {
    
    internal func constraintConstantTargetValueFor(layoutAttribute: LayoutAttribute) -> CGFloat 

对应.offset(20)

priority: ConstraintPriorityTarget
ConstraintPriorityTarget,UInt,Float,CGFloat等一系列数据转换为UILayoutPriority

对应.priorityLow()

constraint: Constraint?
这是一个懒加载的数据,对约束的操作都放在这个里面进行,我们到最后再来讲解

当前数据只写入了前两个成员变量,之后成员变量如何写入会慢慢介绍。

3.1.3 ConstraintMakerExtendable

**继承类 ConstraintMakerRelatable **

本方法提供的是链式调用

make.left.right.top.....

make.left 我们已经知道,等价于生成一个ConstraintMakerExtendable

let ext: ConstraintMakerExtendable = self.makeExtendableWithAttributes(.left)

而后续的.right.top.....则是

public class ConstraintMakerExtendable: ConstraintMakerRelatable {
    
    public var left: ConstraintMakerExtendable {
        self.description.attributes += .left
        return self
    }
} 

let ext: ConstraintMakerExtendable = self.makeExtendableWithAttributes(.left)
ext.description.attributes += .right
ext.description.attributes += .top

3.1.4 ConstraintMakerRelatable

提供 equalTo, equalToSuperview, ....等一系列方法

**继承类 ConstraintMakerEditable **

这一系列方法都是基于 relateTo 函数

之前我们说过在

 let description = ConstraintDescription(
        item: marqueeLabel,
        attributes: ConstraintAttributes.left
    )
self.descriptions.append(description)

此时description还只填写了前两个成员变量。

在relatedTo 函数根据other与relation的类型 ,填写description中的数据:


image.png
closure 类型 生成数据
top.equalTo(a.snp.bottom) ConstraintItem related = other,constant = 0.0
top.equalToSuperview() ConstraintView related = ConstraintItem(target: other, attributes: ConstraintAttributes.none),constant = 0.0
height.equalTo(65) ConstraintConstantTarget related = ConstraintItem(target: nil, attributes: ConstraintAttributes.none),constant = other
   let editable = ConstraintMakerEditable(self.description)
        editable.description.sourceLocation = (file, line)
        editable.description.relation = relation
        editable.description.related = related
        editable.description.constant = constant

3.1.5 ConstraintMakerEditable

**继承类 ConstraintMakerPriortizable **

提供 multipliedBy, dividedBy, offset, inset等一系列方法, 设置常量数据, 填充了ConstraintDescription中如下的数据:

image.png

至此,我们可以知道

marqueeLabel.snp.makeConstraints {(make) in
  ~~** make.left.equalToSuperview()**~~
 }
 
{(make:ConstraintMaker) in
    let maker = ConstraintMaker(item: item)
    let description = ConstraintDescription(
        item: marqueeLabel,
        attributes: ConstraintAttributes.left
    )
}

这段代码的目标就是生产一个ConstraintDescription,然后将其存储在ConstraintMaker的descriptions数组中中。

marqueeLabel.snp.makeConstraints {(make) in
  ~~** make.left.equalToSuperview()**~~
   make.right.equalToSuperview()
    make.top.equalToSuperview()
     make.bottom.equalToSuperview()
 }

随着block中数据的增多,descriptions逐渐被填满。下面一步就是用descriptions来生成对应的约束了。

3.2、约束描述翻译

涉及类: Constraint
上面我们说的ConstraintDescription,还有最后一个成员变量 constraint: Constraint?,这个变量就是有desc翻译出来的约束,这这个类中,我们最终将descriptions转义成NSLayoutConstraint

3.2.1、Constraint

涉及类:ConstraintItem
Constraint是ConstraintDescription到NSLayoutConstraint中转站,其初始化需要如下参数:

变量名 变量类型 描述
from ConstraintItem 约束将实现在那个view上,这个view上有哪些约束条件
to ConstraintItem 约束相关的View或者状态
relation ConstraintRelation 约束关系,equal,less,greater
multiplier ConstraintMultiplierTarget 约束比例
constant ConstraintConstantTarget 约束值
layoutConstraints LayoutConstraint 约束值

本类所做的就是将约束关系转换成为所要布局的视图和对应的布局视图的位置关系。

然后,统一对每个NSLayoutConstraint执行activate,其本质是调用activateIfNeeded函数.

func activateIfNeeded(updatingExisting: Bool = false) {
        let layoutConstraints = self.layoutConstraints
        if updatingExisting {
           updateExistData()
        } else {
            NSLayoutConstraint.activate(layoutConstraints)
            item.add(constraints: [self])
        }
    }

到这一步,基本的约束就已经构建成功了。

学到的东西

  1. 通过协议使用的外观模式
public protocol ConstraintRelatableTarget {
}

extension Int: ConstraintRelatableTarget {
}

......

extension ConstraintInsets: ConstraintRelatableTarget {
}

extension ConstraintItem: ConstraintRelatableTarget {
}

extension ConstraintView: ConstraintRelatableTarget {
}
  1. 通过别名使用的外观模式
#if os(iOS) || os(tvOS)
    import UIKit
#else
    import AppKit
#endif

#if os(iOS) || os(tvOS)
    public typealias ConstraintView = UIView
#else
    public typealias ConstraintView = NSView
#endif
  1. 不使用库时 AutoLayout的写法
//https://www.jianshu.com/p/d67395deb694
view.translatesAutoresizingMaskIntoConstraints = false //自动把frame转换成约束,可能导致冲突,需要关闭

marqueeLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 0).isActive = true
marqueeLabel.rightAnchor.constraint(equalTo: rightAnchor, constant: 0).isActive = true
marqueeLabel.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true
marqueeLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
上一篇下一篇

猜你喜欢

热点阅读