美文网首页
iOS SnapKit源码解析(二)进入闭包

iOS SnapKit源码解析(二)进入闭包

作者: Rimson | 来源:发表于2019-05-02 11:03 被阅读0次

    在上一篇 iOS SnapKit源码解析(一)makeConstraints的过程
    中,简单介绍了snp.makeConstraints的调用栈,没有描述闭包之内代码的运行过程,这篇文章就探究一下闭包之内发生了什么。

    简单用法

        button.snp.makeConstraints { (make) in
            make.size.equalTo(CGSize(width: 100, height: 100))
            make.center.equalTo(bgView)
        }
    
        label.snp.makeConstraints { (make) in
            make.top.equalTo(button.snp.bottom).offset(30) 
            make.size.equalTo(CGSize(width: 200, height: 50))
            make.centerX.equalTo(button)
        }
    

    可以看到,make是闭包的关键所在,接下来继续研究上一篇探索过的ConstraintMaker

    ConstraintMaker

    这可以说是整个SnapKit中最关键的类,它将ConstraintViewConstraint联系到了一起,是将约束附加到控件上的工具。上篇文章中说道ConstraintMakerprepareConstraints方法中闭包的执行就避开不谈了,本文将从这里入手:

        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
        }
    

    closure(maker)执行了闭包中的代码,例如:

    make.size.equalTo(CGSize(width: 100, height: 100))
    

    这里的make.size到底是怎么实现的呢?

    不难发现makeConstraintMaker)有很多成员变量:

    public class ConstraintMaker {
        
        public var left: ConstraintMakerExtendable {
            return self.makeExtendableWithAttributes(.left)
        }
        
        public var size: ConstraintMakerExtendable {
            return self.makeExtendableWithAttributes(.size)
        }
    
        // ......
    }
    

    可以看到这些ConstraintMakerExtendable变量都是甩锅侠,都交给了ConstraintMakermakeExtendableWithAttributes方法处理:

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

    首先创建了一个ConstraintDescription,然后将其作为参数,创建ConstraintMakerExtendable并返回。

    ConstraintMakerExtendable

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

    这个类并没有构造函数,而是千篇一律的类似sizelayout属性,可以自行脑补,将size换成其他诸如widthcenterX等等众多属性,简单来说这个类的作用还是甩锅。

    虽然没有找到构造函数,但是return self说明make.size已经将这个ConstraintMakerExtendable返回了,紧接着调用equalTo方法。因为这个类即没有这个方法,也没有我们想要的构造函数,于是去父类看看。

    ConstraintMakerRelatable

    public class ConstraintMakerRelatable {
        
        internal let description: ConstraintDescription
        
        internal init(_ description: ConstraintDescription) {
            self.description = description
        }
    
        @discardableResult
        public func equalTo(_ other: ConstraintRelatableTarget, _ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
            return self.relatedTo(other, relation: .equal, file: file, line: line)
        }
    
        internal func relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation, file: String, line: UInt) -> ConstraintMakerEditable {
            // 省略代码
            let editable = ConstraintMakerEditable(self.description)
            editable.description.sourceLocation = (file, line)
            editable.description.relation = relation
            editable.description.related = related
            editable.description.constant = constant
            return editable
        }
    }
    

    到这里算是会师了,不仅找到了接受ConstraintDescription为参数的构造函数,而且equalTo也在这里。这里equalTo@discardableResult值得注意,说明有两种情况,一种是像我们上面的例子一样,最多只执行到equalTo,忽略返回值;另一种就是可以在返回值上进行更进阶的操作。

    internal enum ConstraintRelation : Int {
        case equal = 1
        case lessThanOrEqual
        case greaterThanOrEqual
        
        internal var layoutRelation: LayoutRelation {
            get {
                switch(self) {
                case .equal:
                    return .equal
                case .lessThanOrEqual:
                    return .lessThanOrEqual
                case .greaterThanOrEqual:
                    return .greaterThanOrEqual
                }
            }
        }
    }
    

    不管是equalTo,还是lessThanOrEqualTo或者greaterThanOrEqual,最终都是调用relatedTo,只不过传入的枚举类ConstraintRelation不同而已。

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

    而像equalToSuperview这种涉及到Superview的关系,都是在方法内部先获取到Superview,然后将其作为约束的目标对象,再通过对应的equalTo返回结果。

        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
        }
    

    relatedTo主要是对传入other: ConstraintRelatableTarget进行转型判断,根据不同结果对related: ConstraintItemconstant: ConstraintConstantTarget进行赋值,最终构造并返回一个ConstraintMakerEditable对象。

    ConstraintMakerEditable

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

    在这里又见到了@discardableResult,如果按照最初的例子make.size.equalTo(),到这里已经执行完毕了。这些方法只不过是更深入的操作,例如offset设置偏移量,设置约束优先级等等。

    ConstraintDescription

    精简代码如下:

    public class ConstraintDescription {
        internal var attributes: ConstraintAttributes
        // 省略很多成员变量  
        internal lazy var constraint: Constraint? = {   
            return Constraint(
                // 利用那些成员变量构造Constraint
            )
        }()
        
        // MARK: Initialization
        
        internal init(item: LayoutConstraintItem, attributes: ConstraintAttributes) {
            self.item = item
            self.attributes = attributes
        }
        
    }
    

    ConstraintDescription有很多成员变量,其中包括我们需要的attributesconstraint,需要返回constraint时,就利用这些成员变量构造一个新的Constraint并返回。

    相关文章

      网友评论

          本文标题:iOS SnapKit源码解析(二)进入闭包

          本文链接:https://www.haomeiwen.com/subject/uckwnqtx.html