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]

我正在学习Swift,在我的一个模型课上遇到了一个问题。 我试图做的是拥有一个延迟加载的属性,当它所基于的数据发生更改时,该属性可以“失效”。(类似这样的内容:)

我现在拥有的是这样的东西:

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