Swift 使用一个常量值作为;“无”;(无价值)这种情况下的良好做法?
我正在学习Swift,在我的一个模型课上遇到了一个问题。 我试图做的是拥有一个延迟加载的属性,当它所基于的数据发生更改时,该属性可以“失效”。(类似这样的内容:) 我现在拥有的是这样的东西:Swift 使用一个常量值作为;“无”;(无价值)这种情况下的良好做法?,swift,lazy-loading,lazy-evaluation,Swift,Lazy Loading,Lazy Evaluation,我正在学习Swift,在我的一个模型课上遇到了一个问题。 我试图做的是拥有一个延迟加载的属性,当它所基于的数据发生更改时,该属性可以“失效”。(类似这样的内容:) 我现在拥有的是这样的东西: class DataSet { var entries: [Entry] var average: Double? { return self.entries.average } } class DataSet { var entries: [Entry]
class DataSet {
var entries: [Entry]
var average: Double? {
return self.entries.average
}
}
class DataSet {
var entries: [Entry]
var _average: Double?
var average: Double? {
if _average == nil {
_average = self.entries.average
}
return _average
}
}
Array.average
计算Entry
属性的平均值,但如果数组为空,则返回nil
由于计算此平均值可能会非常昂贵,因此我希望延迟执行操作,并存储它的缓存值,并仅在必要时重新计算它(当修改DataSet.entries
时)
下面,我应该这样做:
class DataSet {
var entries: [Entry]
var average: Double? {
return self.entries.average
}
}
class DataSet {
var entries: [Entry]
var _average: Double?
var average: Double? {
if _average == nil {
_average = self.entries.average
}
return _average
}
}
但是,我在处理将重新计算平均值的情况下,以及当数组为空且没有返回有意义的平均值时,都存在问题
因为我知道平均值总是正值,所以我可以使用默认值(例如-1.0)来表示缓存不再有效,nil
表示没有条目,反之亦然(nil
表示必须再次计算平均值;-1.0表示没有条目)
然而,这似乎并不优雅,也不是实现这一行为的“快捷方式”(或者确实如此?)
我该怎么办?使用nil
首先,永远不要使用类型域的特定值来表示没有值。换句话说,不要用负数表示没有值<这里的答案是code>nil
因此,average
应声明为Double?
当条目发生更改时清除缓存
接下来,每次条目发生变异时,您都需要清除缓存。您可以为此使用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
,那么为什么不相应地更改添加到SequenceType
的average
计算属性?您可以使用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
}
}
基本上,只要条目
属性值发生更改,您就可以将缓存的值标记为过期,并使用此标志来知道何时重新计算它。是一个很好的具体解决方案。然而,我建议采取不同的方法。下面是我要做的:
我将制定一个协议,定义需要外部访问的所有重要位
然后,我将使2个结构/类符合此协议
第一个结构/类将是缓存层
第二个结构/类将是实际的实现,只有缓存层才能访问它
缓存层将有一个实际实现结构/类的私有实例,并将有一个变量,如“isCacheValid”,它将通过使基础数据无效的所有变异操作(扩展为计算值,如平均值)设置为false
这种设计使得实际实现的结构/类相当简单,并且与缓存完全无关
缓存层执行所有缓存职责,完全不知道缓存值是如何计算的(因为它们的计算只是委托给实现类。您绝对不应该使用魔法值,如-1
来表示某些状态。但是我同意您不应该使用nil
来表示“缓存的值无效,必须重新计算”以及“缓存的平均值已计算且为nil
,因为存在零个条目
对象”。建议将计算值设置为nil
的其他解决方案的问题在于,它无法区分“无效”和“已计算但nil
这两种状态。”并且可以调用条目。平均值,即使您可能已经这样做了。诚然,这在计算上可能并不昂贵,但它将两种截然不同的状态合并在一起
一个Swift解决方案是枚举
:
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
}
}
}
这捕获了.Invalidated
和.computed
之间的不同状态,并根据需要延迟重新计算。恐怕最佳实践问题不适合堆栈溢出,因为答案只能基于观点,而不能基于事实。顺便说一下,在上面的示例中,您声明了数据集
var average:Double{…}
的计算属性,但我假设条目。如果没有条目
对象,average将返回nil
。所以我认为声明应该是var average:Double?{…}
。从实用的角度来看,在几乎所有情况下,空数组的平均值0.0比nil
更合适。在旧的Objective-C时代,我们习惯于将nil
视为不在乎;-)@瓦迪亚-我不同意。nil
意味着没有值,它的优点是永远不会被误认为是真正的计算值。你只是在使用另一个神奇的值(更糟糕的是,它与潜在的有效值无法区分)。使用0
并不比他最初的-1
方法好。此外,他明确告诉我们“Array.average
计算Entry
属性的平均值,但如果数组为空,则返回nil
”(这是正确的方法,IMHO)。您也可以在数组中使用didSet
来监视突变,而不是将其私有化。@GriffeyDog:您是对的。我更新了我的答案,谢谢!这看起来是最简单的,在大多数情况下可能是最快的解决方案。但是,正如我所说,SequenceType.average
在t时返回nil