如何在 Swift 中的变异结构上正确地创建惰性派生属性?
Posted
技术标签:
【中文标题】如何在 Swift 中的变异结构上正确地创建惰性派生属性?【英文标题】:How to properly make a lazy derived property on a mutating struct in Swift? 【发布时间】:2015-03-06 15:21:00 【问题描述】:我正在制作一个具有非常昂贵的计算派生值的变异结构。所以我想做的是懒惰地计算这个派生值并存储结果,直到结构再次变异,此时派生值不再有效,需要重新计算。
(失败)选项 1:生成的属性
如果派生值是生成的属性(如下所示),则始终返回正确的值,但始终会重新计算。
(失败)选项 2:延迟加载属性
如果它是惰性属性,则计算只进行一次......永远。因此,一旦结构发生变异,派生值就是错误的,不会被重新计算。另外,如果我从结构中分配一个常量值,我将无法访问该属性。
在 Swift 1.2 中是否有任何可能的解决方案,或者我需要提交雷达文件吗?
struct Struct
var value: Int
// Option 1: Generated property
var derivedValue: Int
println("Doing expensive calculation")
return self.value * 2
// Option 2: Lazy property
lazy var derivedValue: Int =
println("Doing expensive calculation")
return self.value * 2
()
init(value: Int)
self.value = value
mutating func mutate()
value = random()
var test = Struct(value: 2)
test.derivedValue
test.derivedValue // If not lazy, expensive calculation is done again here
test.mutate()
test.derivedValue // If lazy, this has wrong value
let test2 = test
test2.derivedValue // Compiler error if using lazy implementation
【问题讨论】:
如果你也发布了你的结构的惰性版本会很有帮助。 现在添加了惰性实现 【参考方案1】:使用嵌入式类可以绕过改变结构的限制。这使您可以使用按值类型,在需要计算之前不会运行昂贵的计算,但之后仍会记住结果。
下面的示例Number
struct 以与您描述的一样的方式计算并记住它的 square 属性。数学本身的效率低得离谱,但它是一种说明解决方案的简单方法。
struct Number
// Store a cache in a nested class.
// The struct only contains a reference to the class, not the class itself,
// so the struct cannot prevent the class from mutating.
private class Cache
var square: Int?
var multiples: [Int: Int] = [:]
private var cache = Cache()
// Empty the cache whenever the struct mutates.
var value: Int
willSet
cache = Cache()
// Prevent Swift from generating an unwanted default initializer.
// (i.e. init(cache: Number.Cache, value: Int))
init(value: Int)
self.value = value
var square: Int
// If the computed variable has been cached...
if let result = cache.square
// ...return it.
print("I’m glad I don’t have to do that again.")
return result
else
// Otherwise perform the expensive calculation...
print("This is taking forever!")
var result = 0
for var i = 1; i <= value; ++i
result += value
// ...store the result to the cache...
cache.square = result
// ...and return it.
return result
// A more complex example that caches the varying results
// of performing an expensive operation on an input parameter.
func multiple(coefficient: Int) -> Int
if let result = cache.multiples[coefficient]
return result
else
var result = 0
for var i = 1; i <= coefficient; ++i
result += value
cache.multiples[coefficient] = result
return result
这就是它的表现:
// The expensive calculation only happens once...
var number = Number(value: 1000)
let a = number.square // “This is taking forever!”
let b = number.square // “I’m glad I don’t have to do that again.”
let c = number.square // “I’m glad I don’t have to do that again.”
// Unless there has been a mutation since last time.
number.value = 10000
let d = number.square // “This is taking forever!”
let e = number.square // “I’m glad I don’t have to do that again.”
// The cache even persists across copies...
var anotherNumber = number
let f = anotherNumber.square // “I’m glad I don’t have to do that again.”
// ... until they mutate.
anotherNumber.value = 100
let g = anotherNumber.square // “This is taking forever!”
作为一个更实际的例子,我在日期结构上使用了这种技术,以确保在日历系统之间进行转换的重要计算尽可能少地运行。
【讨论】:
【参考方案2】:这是一个非常有趣的问题。我在这里有一些不同的想法可能会有所帮助。
首先,您稍微误用了lazy
属性的概念。你只能拥有lazy stored properties,因为惰性所做的只是延迟执行,直到它第一次执行。从那时起,该值在属性中为 stored
。您正在处理一个不能以这种方式使用的计算属性。您当然可以提交雷达,但我认为这是一个失败的原因,因为您的用例不是有效的惰性案例 IMO。
话虽如此,我认为您有几个选择。
选项 1 - 使用具有属性观察器的类
class Calculator
var value: Int
didSet
valueChanged = true
var valueChanged = false
var derivedValue: Int
if valueChanged
println("Doing expensive calculation")
valueChanged = false
return self.value * 2
init(value: Int)
self.value = value
func mutate()
value = random()
这里的优点是您仍然可以在调用属性时延迟计算derivedValue
。缺点是您不再使用“按值”对象。
选项 2 - 在变异方法中计算昂贵的值
struct SortOfLazyCalculator
var value: Int
var expensiveComputedValue: Int = 0 // just guessing
var derivedValue: Int
return self.value * 2
init(value: Int)
self.value = value
mutating func mutate()
value = random()
expensiveComputedValue = random() // not sure what the expensive calculation is
这种方法的优点是您仍然可以保留“按值”对象,但您必须在突变时计算昂贵的值。您不能在 derivedValue
属性内执行此操作,因为您不能在结构的计算属性内更改 self
。
选项 3 - 使用静态结构监控值变化
struct Struct
var value: Int
var derivedValue: Int
struct Static static var previousValue: Int?
if Static.previousValue == nil
println("Setting previous value since it is nil")
Static.previousValue = value
if value != Static.previousValue!
println("Doing expensive calculation")
Static.previousValue = value
return self.value * 2
init(value: Int)
self.value = value
mutating func mutate()
value = random()
这种方法允许您保留“按值”对象,同时还允许您懒惰地计算昂贵的值。但这里的主要问题是这仅适用于单个对象。如果您要创建多个对象,这是一种不好的方法。
总结
不幸的是,这不是惰性属性的有效用例。但是,还有其他方法可以解决这个问题。希望其中之一就足够了。根据您提供的所有信息,我大胆猜测选项 2 可能是您最好的选择。
【讨论】:
感谢您的详尽回复。一个惰性存储的属性正是我想要的。由于该操作的计算成本很高,因此我想延迟执行并在计算后存储它。我只是希望它在结构发生突变时失效并返回其“惰性”状态。我同意 Option 2 最适合保持不变性并且仍然具有没有任何静态内容的值类型。问题是该属性不再是懒惰的了。因此,如果我创建结构,对其运行一系列不同的突变,然后使用派生值,则将为每个突变重新计算该值。 我完全明白你想要做什么,但同样,你在滥用lazy
属性。 lazy
属性仅在初始化之前是惰性的,这不是您想要做的。所以当你说你想要一个惰性属性时,我认为你绝对不想要一个惰性属性,因为这不是它们的工作方式。由于结构类型的不可变性,您只能在可变函数中更新昂贵计算的值。
是的,目前lazy
关键字根本不适用于值类型。我想我会归档雷达。
酷哥们,祝你好运。如果您认为答案足够,您可以将其标记为足够吗?干杯。以上是关于如何在 Swift 中的变异结构上正确地创建惰性派生属性?的主要内容,如果未能解决你的问题,请参考以下文章