zoukankan      html  css  js  c++  java
  • snapkit equalto和multipliedby方法

    最近在使用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给当前对象绑定一个同名的属性,每次使用时获取绑定的属性的值,不懂,希望知道的同学不吝赐教。

  • 相关阅读:
    学习PyQt5(二):PyQt5布局管理
    学习PyQt5(一):安装PyQt5以及在PyCharm上配置PyQt5
    Day037--Python--线程的其他方法,GIL, 线程事件,队列,线程池,协程
    Day036--Python--线程
    Day035--Python--管道, Manager, 进程池, 线程切换
    Day034--Python--锁, 信号量, 事件, 队列, 生产者消费者模型, joinableQueue
    Day033--Python--进程
    Day032--Python--操作系统, process进程
    Day30--Python--struct, socketserver
    Day29--Python--缓冲区, 粘包
  • 原文地址:https://www.cnblogs.com/shenyuiOS/p/9706832.html
Copyright © 2011-2022 走看看