【问题标题】:Lazy calculated variable惰性计算变量
【发布时间】:2014-09-22 18:06:48
【问题描述】:

我正在尝试创建一个具有惰性计算变量 magnitude 的 Vector 结构,但我似乎找不到方法。

这就是我所拥有的:

struct Vector {

    var x: Double = 0.0 {
        didSet {
            magnitudeActual = -1.0
        }
    }
    var y: Double = 0.0 {
        didSet {
            magnitudeActual = -1.0
        }
    }

    var magnitudeActual: Double = 0.0

    var magnitude: Double {
        if magnitudeActual < 0.0 {
            magnitudeActual = sqrt(x * x + y * y) //cannot assign to "magnitudeActual" in self
        }
        return magnitudeActual
    }

    init() {}

    init(_ x: Double, _ y: Double) {
        self.x = x
        self.y = y
    }

}

我已经尝试了很多方法来让它工作,但似乎没有任何工作。此外,willGet 也不错,但不存在。

【问题讨论】:

    标签: swift


    【解决方案1】:

    From the docs:

    在实例方法中修改值类型

    结构和枚举是值类型。默认情况下,值类型的属性不能在其实例方法中修改。

    但是,如果您需要在特定方法中修改结构或枚举的属性,您可以选择更改该方法的行为。然后,该方法可以在该方法内改变(即更改)其属性,并且它所做的任何更改都会在该方法结束时写回原始结构。该方法还可以为其隐含的 self 属性分配一个全新的实例,该新实例将在方法结束时替换现有实例。


    那么,struct 不能修改自身,除非进行修改的函数被标记为 mutating。这意味着您需要为该属性定义一个适当的get 函数。

    var magnitude: Double {
    mutating get {
        if magnitudeActual < 0.0 {
            NSLog("Recalc") // just to make sure it's caching the result properly.
            magnitudeActual = sqrt(x * x + y * y)
        }
        return magnitudeActual
    }
    }
    

    现在我们可以这样做了

    var v = Vector(3, 4)
    v.magnitude // Recalc 5
    v.magnitude // 5
    
    v.x = 5
    v.y = 12
    v.magnitude // Recalc 13
    

    【讨论】:

    • 谢谢!我试图在某些地方(比如在 var 前面)放置变异,但从来没有把它放在 get 前面,特别是因为它是可选的
    • 最近有人试过这个(Beta 6)吗?我想我在 Playground 的 v.magnitude 行中遇到了内部编译器错误。症状是它没有完成或给出明确的错误。
    【解决方案2】:

    我玩弄了你的代码和 Alex 提出的变异 getter,我发现

    init(_ x: Double, _ y: Double) {
        self.x = x
        self.y = y
    }
    

    似乎没有调用 x 和 y 的设置器,或者至少没有调用通知 didSet 和 willSet。如果像这样构造,magnitudeActual 将是 0

    var v = Vector(3,4)
    

    并且幅度不会重新计算并返回 0。

    但是如果像这样构造

    var v = Vector()
    v.x = 3
    v.y = 4
    

    setter 被调用,magnitudeActual 将是 -1 并且幅度将被重新计算。

    按照 GoZoner 的建议,解决方法是将 magnitudeActual 的默认值设置为 -1。

    我不知道这是设计行为还是错误。


    我使用的代码:

    struct Vector {
    
        var x: Double = 0.0 {
        didSet {
            println("set x")
    
            magnitudeActual = -1.0
        }
        }
        var y: Double = 0.0 {
        didSet {
            println("set y")
            magnitudeActual = -1.0
        }
        }
    
        var magnitudeActual: Double = -1.0
    
        var magnitude: Double {
        mutating get {
            if magnitudeActual < 0.0 {
                println("Recalc") // just to make sure it's caching the result properly.
                magnitudeActual = sqrt(x * x + y * y)
            }
            return magnitudeActual
        }
        }
    
        init() {}
    
        init(_ x: Double, _ y: Double) {
            self.x = x
            self.y = y
        }
    }
    

    【讨论】:

    • 啊,我从来没有注意到。感谢您解决不可避免的问题。
    【解决方案3】:

    为什么不只是:

    var magnitude: Double {
       return sqrt(x * x + y * y)
    }
    

    如果您真的只想计算一次,请使用带有惰性成员的立即执行闭包:

    lazy var magnitude: Double = {
       return sqrt(x * x + y * y)
    }()
    

    因为它是惰性的,所以它在第一次访问之前不会尝试执行分配。因为赋值是针对闭包的结果,所以闭包将针对赋值执行,让该值在第一次访问时计算。

    下面是一个简单的行为示例:

    class MyClass {
        var x :Double = 10
        lazy var magnitude: Double = {
           return self.x
        }()
    }
    
    var obj = MyClass()
    
    obj.x = 12
    obj.magnitude // 12 (magnitude was calculated right here after x was changed)
    
    obj.x = 14
    obj.magnitude // 12 (magnitude did not change)
    
    obj.magnitude = 8
    obj.magnitude // 8 (it changed)
    

    【讨论】:

      【解决方案4】:

      当然,您节省的很少,很少购买优化magnitude 的计算。您可以简单地完成并撒上灰尘:

      class Point {
       var x : Double;
       var y : Double;
       var magnitude : Double { return sqrt (x*x + y*y) }
       var angle     : Double { return atan (x, y) }
      }
      

      但是,如果您确实需要优化,

      class Point {
        var x : Double { didSet { updateMagnitude() }}
        var y : Double { didSet { updateMagnitude() }}
        var magnitude : Double
      
        func updateMagnitude () { self.magnitude = sqrt (x*x + y*y) }
      
        init () {
          // ...
          updateMagnitude()
        }
      }
      

      【讨论】:

      • 您的优化可能会导致许多不必要的重新计算,因为它会触发两次更新以设置一对新的 x 和 y。 OP 也希望仅在真正使用时才评估幅度。
      • 任何人担心额外的sort() 是a)不必要的担心或b)进入一些高度专业化的计算算法。唉,OP 提出了一个一般性问题,并且从我这里得到了代码和关于过早优化的警告。
      猜你喜欢
      • 2015-01-19
      • 2018-06-11
      • 2011-11-01
      • 2017-03-04
      • 2016-07-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多