我正在学习Swift,我在我的一个模型课中遇到了问题。 我正在尝试做的是拥有一个延迟加载的属性,当它基于更改的数据时可以“无效”。 (像这样:here)
我现在拥有的是这样的:
class DataSet {
var entries: [Entry]
var average: Double? {
return self.entries.average
}
}
Array<Entry>.average
计算Entry
属性的平均值,但如果数组为空,则返回nil 。
由于此平均值的计算成本可能很高,我希望 lazily 和存储缓存值并仅在必要时重新计算(当DataSet.entries
已修改。)
现在,在https://stackoverflow.com/a/25954243/2382892之后,我应该这样做:
class DataSet {
var entries: [Entry]
var _average: Double?
var average: Double? {
if _average == nil {
_average = self.entries.average
}
return _average
}
}
但是我遇到了处理平均值要重新计算的情况,以及数组为空并且没有有意义的平均值返回的问题。< / p>
因为我知道平均值总是正数,所以我可以使用默认值(例如-1.0)来表示缓存不再有效,nil
表示没有条目,反之亦然(nil
表示必须再次计算平均值;没有条目时为-1.0。
然而,这似乎并不优雅,或“Swift way”来实现这种行为(或者确实是这样?)。
我该怎么办?
答案 0 :(得分:2)
你绝对不应该使用魔术值,比如-1
来表示某种状态。但我同意您不应使用nil
来表示“缓存的值已失效并且必须重新计算”以及“已缓存的平均值已计算且为nil
,因为存在零{ {1}}对象“。建议将计算值设置为Entry
的其他解决方案的问题在于,它无法区分“无效”和“计算但nil
”两种状态,并且可能会调用{{1即使你可能已经这样做了。不可否认,这可能在计算上并不昂贵,但它将两个截然不同的状态混为一谈。
一个Swift解决方案是nil
:
entries.average
这会捕获enum
和class DataSet {
private enum CachedValue {
case Calculated(Double?)
case Invalidated
}
var entries = [Entry]() {
didSet {
cachedAverage = .Invalidated
}
}
private var cachedAverage: CachedValue = .Invalidated
var average: Double? {
switch cachedAverage {
case .Calculated(let result):
return result
case .Invalidated:
let result = entries.average
cachedAverage = .Calculated(result)
return result
}
}
}
之间的不同状态,并根据需要重新计算。
答案 1 :(得分:1)
首先,永远不要使用类型域的特定值来表示缺少值。换句话说,不要使用负数表示没有值。 nil
就是答案。
因此average
应声明为Double?
。
接下来,每次entries
发生变异时都需要清除缓存。您可以使用didSet
。
class DataSet {
private var entries: [Entry] = [] {
didSet { cachedAverage = nil }
}
private var cachedAverage: Double?
var average: Double? {
if cachedAverage == nil {
cachedAverage = self.entries.average
}
return cachedAverage
}
}
最后,如果您认为空数组的average
应该是nil
,那么为什么不相应地更改添加到average
的{{1}}计算属性}?
答案 2 :(得分:1)
您可以使用didSet
:
class DataSet {
var entries: [Entry] {
didSet {
/// just mark the average as outdated, it will be
/// recomputed when someone asks again for it
averageOutOfDate = true
}
}
/// tells us if we should recompute, by default true
var averageOutOfDate = true
/// cached value that avoid the expensive computation
var cachedAverage: Double? = nil
var average: Double? {
if averageOutOfDate {
cachedAverage = self.entries.average
averageOutOfDate = false
}
return cachedAverage
}
}
基本上,只要entries
属性值发生更改,就会将缓存的值标记为过时,并使用此标志知道何时重新计算它。
答案 3 :(得分:1)
appzYourLife's answer是一个很好的具体解决方案。但是,我建议采用不同的方法。这就是我要做的事情:
我会制定一个协议,定义所有需要外部访问的重要位。
然后我会使2个结构/类符合此协议。
第一个struct / class将是一个缓存层。
第二个struct / class将是实际的实现,只有缓存层才能访问它。
缓存层将具有实际实现struct / class的私有实例,并且将具有诸如&#34; isCacheValid&#34;之类的变量,该变量将被所有变异操作设置为false
使基础数据无效(以及通过扩展,计算出的值,例如平均值)。
这种设计使得实际的实现struct / class非常简单,并且与缓存完全无关。
缓存层执行所有缓存任务,完全不知道如何计算缓存值的细节(因为他们的计算只是委托给实现类。