欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

使用SnapKit遇到的问题

程序员文章站 2022-06-11 11:48:22
最近在使用snapkit过程中遇到一个问题,在github上搜索之后发现另外一个有趣的问题 问题链接 看起来很理所当然的,明显不可以这样写,但是具体是什么原因呢,明明没有报任何错误和警告,但是.multipliedBy()方法却没有效果,那我们来看一下snapkit源码。 1.首先点进equalTo ......

最近在使用snapkit过程中遇到一个问题,在github上搜索之后发现另外一个有趣的问题 问题链接

frameimagecontainer.snp.makeconstraints({ (make) in 
    make.width.equalto(295).multipliedby(0.2) 
    make.height.equalto(355).multipliedby(0.2) 
    make.top.equaltosuperview().offset(self.view.frame.height/8) 
    make.centerx.equaltosuperview(); 
})

看起来很理所当然的,明显不可以这样写,但是具体是什么原因呢,明明没有报任何错误和警告,但是.multipliedby()方法却没有效果,那我们来看一下snapkit源码。

1.首先点进equalto()方法,代码是这样的:

@discardableresult
    public func equalto(_ other: constraintrelatabletarget, _ file: string = #file, _ line: uint = #line) -> constraintmakereditable {
        return self.relatedto(other, relation: .equal, file: file, line: line)
    }

再点进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
    }

可以看到上面红色部分,此时other可以转换为constraintconstanttarget类型,设置related的target为nil,attributes为none,constant设置为other,最后将这些变量赋值给description属性保存。

2.multipliedby()方法:

@discardableresult
    public func multipliedby(_ amount: constraintmultipliertarget) -> constraintmakereditable {
        self.description.multiplier = amount
        return self
    }

可以看到,multipliedby方法中只是简单的赋值,将amout值复制到description中保存。

3.再来看一下makeconstraints()方法:

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

首先将要添加约束的对象封装成constraintmaker对象,再把它作为参数调用closure,开始添加约束。closure中的每条语句会先被解析constraintdescription对象,添加到maker的descriptions属性中。 在第一个for循环中可以看到,每次循环从descriptions中取出一条description,判断是否改description是否有constraint属性;

constraint属性使用的懒加载,值为:

internal lazy var constraint: constraint? = {
        guard let relation = self.relation,
              let related = self.related,
              let sourcelocation = self.sourcelocation else {
            return nil
        }
        let from = constraintitem(target: self.item, attributes: self.attributes)
        
        return constraint(
            from: from,
            to: related,
            relation: relation,
            sourcelocation: sourcelocation,
            label: self.label,
            multiplier: self.multiplier,
            constant: self.constant,
            priority: self.priority
        )
    }()

先判断relation,related,sourcelocation的值是否为nil,若为nil则返回nil,否则就根据description属性创建一个constraint对象并返回。

constraint的构造方法:

    internal init(from: constraintitem,
                  to: constraintitem,
                  relation: constraintrelation,
                  sourcelocation: (string, uint),
                  label: string?,
                  multiplier: constraintmultipliertarget,
                  constant: constraintconstanttarget,
                  priority: constraintprioritytarget) {
        self.from = from
        self.to = to
        self.relation = relation
        self.sourcelocation = sourcelocation
        self.label = label
        self.multiplier = multiplier
        self.constant = constant
        self.priority = priority
        self.layoutconstraints = []

        // get attributes
        let layoutfromattributes = self.from.attributes.layoutattributes
        let layouttoattributes = self.to.attributes.layoutattributes

        // get layout from
        let layoutfrom = self.from.layoutconstraintitem!

        // get relation
        let layoutrelation = self.relation.layoutrelation

        for layoutfromattribute in layoutfromattributes {
            // get layout to attribute
            let layouttoattribute: layoutattribute
            #if os(ios) || os(tvos)
                if layouttoattributes.count > 0 {
                    if self.from.attributes == .edges && self.to.attributes == .margins {
                        switch layoutfromattribute {
                        case .left:
                            layouttoattribute = .leftmargin
                        case .right:
                            layouttoattribute = .rightmargin
                        case .top:
                            layouttoattribute = .topmargin
                        case .bottom:
                            layouttoattribute = .bottommargin
                        default:
                            fatalerror()
                        }
                    } else if self.from.attributes == .margins && self.to.attributes == .edges {
                        switch layoutfromattribute {
                        case .leftmargin:
                            layouttoattribute = .left
                        case .rightmargin:
                            layouttoattribute = .right
                        case .topmargin:
                            layouttoattribute = .top
                        case .bottommargin:
                            layouttoattribute = .bottom
                        default:
                            fatalerror()
                        }
                    } else if self.from.attributes == self.to.attributes {
                        layouttoattribute = layoutfromattribute
                    } else {
                        layouttoattribute = layouttoattributes[0]
                    }
                } else {
                    if self.to.target == nil && (layoutfromattribute == .centerx || layoutfromattribute == .centery) {
                        layouttoattribute = layoutfromattribute == .centerx ? .left : .top
                    } else {
                        layouttoattribute = layoutfromattribute
                    }
                }
            #else
                if self.from.attributes == self.to.attributes {
                    layouttoattribute = layoutfromattribute
                } else if layouttoattributes.count > 0 {
                    layouttoattribute = layouttoattributes[0]
                } else {
                    layouttoattribute = layoutfromattribute
                }
            #endif

            // get layout constant
            let layoutconstant: cgfloat = self.constant.constraintconstanttargetvaluefor(layoutattribute: layouttoattribute)

            // get layout to
            var layoutto: anyobject? = self.to.target

            // use superview if possible
            if layoutto == nil && layouttoattribute != .width && layouttoattribute != .height {
                layoutto = layoutfrom.superview
            }

            // create layout constraint
            let layoutconstraint = layoutconstraint(
                item: layoutfrom,
                attribute: layoutfromattribute,
                relatedby: layoutrelation,
                toitem: layoutto,
                attribute: layouttoattribute,
                multiplier: self.multiplier.constraintmultipliertargetvalue,
                constant: layoutconstant
            )

            // set label
            layoutconstraint.label = self.label

            // set priority
            layoutconstraint.priority = layoutpriority(rawvalue: self.priority.constraintprioritytargetvalue)

            // set constraint
            layoutconstraint.constraint = self

            // append
            self.layoutconstraints.append(layoutconstraint)
        }
    }

重点看红色部分,遍历layoutattributes,并根据layoutattribute的值生成一个layoutconstraint对象添加到layoutconstraints数组中。layoutconstraint继承自系统类nslayoutconstraint。

4.最后再看3中的第二个for循环,使用activeifneeded()方法激活约束:

 internal func activateifneeded(updatingexisting: bool = false) {
        guard let item = self.from.layoutconstraintitem else {
            print("warning: snapkit failed to get from item from constraint. activate will be a no-op.")
            return
        }
        let layoutconstraints = self.layoutconstraints

        if updatingexisting {
            var existinglayoutconstraints: [layoutconstraint] = []
            for constraint in item.constraints {
                existinglayoutconstraints += constraint.layoutconstraints
            }

            for layoutconstraint in layoutconstraints {
                let existinglayoutconstraint = existinglayoutconstraints.first { $0 == layoutconstraint }
                guard let updatelayoutconstraint = existinglayoutconstraint else {
                    fatalerror("updated constraint could not find existing matching constraint to update: \(layoutconstraint)")
                }

                let updatelayoutattribute = (updatelayoutconstraint.secondattribute == .notanattribute) ? updatelayoutconstraint.firstattribute : updatelayoutconstraint.secondattribute
                updatelayoutconstraint.constant = self.constant.constraintconstanttargetvaluefor(layoutattribute: updatelayoutattribute)
            }
        } else {
            nslayoutconstraint.activate(layoutconstraints)
            item.add(constraints: [self])
        }
    }

当updatingexisting为false时,进入else语句,使用的系统类nslayoutconstraint的方法激活约束:

/* 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(ios 8.0, *)
    open class func activate(_ constraints: [nslayoutconstraint])

并将设置过的约束添加到item的constraintset这个私有属性中:

internal var constraints: [constraint] {
        return self.constraintsset.allobjects as! [constraint]
    }
    
    internal func add(constraints: [constraint]) {
        let constraintsset = self.constraintsset
        for constraint in constraints {
            constraintsset.add(constraint)
        }
    }
    
    internal func remove(constraints: [constraint]) {
        let constraintsset = self.constraintsset
        for constraint in constraints {
            constraintsset.remove(constraint)
        }
    }
    
    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
        
    }

5.通过这个过程不难发现,使用make.width.equalto(295).multipliedby(0.2) 这种方式不能得到想要的结果。在3中constraint的构造方法的红色部分,其实构造layoutconstraint对象时调用的nslayoutconstraint的便利构造器方法:

    /* create constraints explicitly.  constraints are of the form "view1.attr1 = view2.attr2 * multiplier + constant" 
     if your equation does not have a second view and attribute, use nil and nslayoutattributenotanattribute.
     */
    public convenience init(item view1: any, attribute attr1: nslayoutconstraint.attribute, relatedby relation: nslayoutconstraint.relation, toitem view2: any?, attribute attr2: nslayoutconstraint.attribute, multiplier: cgfloat, constant c: cgfloat)

注意上面注释 view1.attr1 = view2.attr2 * multiplier + constant 如果只设置为数字,则相当于view2为nil,所以view1的属性值只能等于constant的值,不会乘以multiplier。

6.终于写完了,哈哈。  demo工程地址,对应的方法已打断点,可以跟着代码一步步调试,有助于理解。

 

 

疑问:在4中最后一部分红色字体的内容,私有属性constraintsset为啥不直接使用,还要使用runtime给当前对象绑定一个同名的属性,每次使用时获取绑定的属性的值,不懂,希望知道的同学不吝赐教。