代码之家  ›  专栏  ›  技术社区  ›  Travis Griggs

在结构中快速记忆/缓存惰性变量

  •  5
  • Travis Griggs  · 技术社区  · 6 年前

    我用斯威夫特喝了古雷酒。现在我有一个有趣的问题我不知道怎么解决。我有一个结构,它是一个容器,例如。

    struct Foo {
        var bars:[Bar]
    }
    

    当我对此进行编辑时,我会创建副本,以便保留撤消堆栈。到现在为止,一直都还不错。就像好的教程所显示的那样。不过,我在这家伙身上使用了一些派生属性:

    struct Foo {
        var bars:[Bar]
    
        var derivedValue:Int {
            ...
        }
    }
    

    在最近的评测中,我注意到a)计算derivedValue的计算有点昂贵/冗余b)在各种用例中并不总是需要计算。

    以我经典的OOP方式,我会把它变成一个记忆/懒惰变量。基本上,在被调用之前将其设为nil,计算一次并存储它,然后在以后的调用中返回所述结果。因为我遵循的是“复制以编辑”模式,所以不变量不会被破坏。

    但如果这个模式是struct,我就不知道如何应用它。我可以做到:

    struct Foo {
        var bars:[Bar]
        lazy var derivedValue:Int = self.computeDerivation()
    }
    

    它起作用,直到结构引用该值本身,例如。

    struct Foo {
        var bars:[Bar]
        lazy var derivedValue:Int = self.computeDerivation()
    
        fun anotherDerivedComputation() {
            return self.derivedValue / 2
        }
    }
    

    此时,编译器抱怨是因为 anotherDerivedComputation 正在引起接收器的更改,因此需要标记 mutating . 让访问器标记为变异是不对的。但对于露齿一笑,我会尝试,但这会产生一系列新的问题。现在在任何地方我都有这样的表达

    XCTAssertEqaul(foo.anotherDerivedComputation(), 20)
    

    编译器之所以抱怨是因为参数隐式地是一个不可变的let值,而不是一个var。

    有没有一个模式我缺少一个带有延迟/延迟/缓存成员的结构?

    3 回复  |  直到 6 年前
        1
  •  3
  •   matt    6 年前

    记忆不会在结构内部发生。记忆的方法是储存一本字典 在一个单独的地方 . 关键是在推导这个值的过程中用到了什么,这个值就是这个值,计算一次。您可以将其设置为结构类型的静态,就像对其进行命名空间一样。

    struct S {
        static var memo = [Int:Int]()
        var i : Int
        var square : Int {
            if let result = S.memo[i] {return result}
            print("calculating")
            let newresult = i*i // pretend that's expensive
            S.memo[i] = newresult
            return newresult
        }
    }
    
    var s = S(i:2)
    s.square // calculating
    s = S(i:2)
    s.square // [nothing]
    s = S(i:3)
    s.square // calculating
    
        2
  •  2
  •   Ole Begemann    6 年前

    我唯一知道的办法就是把懒惰的成员包装到一个类中。这样,包含对象引用的结构可以保持不变,而对象本身可以发生变化。

    几年前我写了一篇关于这个话题的博文: Lazy Properties in Structs . 它详细讨论了细节,并为包装类的设计提出了两种不同的方法,这取决于懒惰成员是否需要来自结构的实例信息来计算缓存值。

        3
  •  1
  •   Travis Griggs    6 年前

    我将问题概括为一个更简单的问题:一个x,y点结构,它希望惰性地计算/缓存r(adius)的值。我用ref包装器包装了一个block闭包,得到了以下结果。我称之为“一次”街区。

    import Foundation
    
    class Once<Input,Output> {
        let block:(Input)->Output
        private var cache:Output? = nil
    
        init(_ block:@escaping (Input)->Output) {
            self.block = block
        }
    
        func once(_ input:Input) -> Output {
            if self.cache == nil {
                self.cache = self.block(input)
            }
            return self.cache!
        }
    }
    
    struct Point {
        let x:Float
        let y:Float
        private let rOnce:Once<Point,Float> = Once {myself in myself.computeRadius()}
    
        init(x:Float, y:Float) {
            self.x = x
            self.y = y
        }
    
        var r:Float {
            return self.rOnce.once(self)
        }
    
        func computeRadius() -> Float {
            return sqrtf((self.x * self.x) + (self.y * self.y))
        }
    }
    
    let p = Point(x: 30, y: 40)
    
    print("p.r \(p.r)")
    

    我选择让OnCEBULD输入一个输入,因为否则将其初始化为一个引用自身的函数是一种痛苦,因为在初始化过程中,自身还不存在,因此更容易推迟到高速缓存/调用站点的链接。 var r:Float )

    推荐文章