在这种情况下,使用常数值作为“零”(没有价值)是一种好的做法吗?

时间:2016-06-30 18:57:50

标签: swift lazy-loading lazy-evaluation

我正在学习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”来实现这种行为(或者确实是这样?)。

我该怎么办?

4 个答案:

答案 0 :(得分:2)

你绝对不应该使用魔术值,比如-1来表示某种状态。但我同意您不应使用nil来表示“缓存的值已失效并且必须重新计算”以及“已缓存的平均值已计算且为nil,因为存在零{ {1}}对象“。建议将计算值设置为Entry的其他解决方案的问题在于,它无法区分“无效”和“计算但nil”两种状态,并且可能会调用{{1即使你可能已经这样做了。不可否认,这可能在计算上并不昂贵,但它将两个截然不同的状态混为一谈。

一个Swift解决方案是nil

entries.average

这会捕获enumclass 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

首先,永远不要使用类型域的特定值来表示缺少值。换句话说,不要使用负数表示没有值。 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非常简单,并且与缓存完全无关。

缓存层执行所有缓存任务,完全不知道如何计算缓存值的细节(因为他们的计算只是委托给实现类。