为一个给定的API建立一套强大的默认值通常可以使它更容易使用,因为这样做可以使每个调用站点保持尽可能的简单,同时还可以在需要时进行定制。
例如,假设我们正在开发一个基于UIKit的视图,该视图使用DateFormatter 来显示一个给定的日期,我们希望它默认使用当前的Date 和一组特定的格式化样式(同时仍然能够指定自定义值)。使用默认的属性值,我们可以像这样实现这一点:
class DateView: UIView {
var date = Date()
var formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}()
...
}
然而,考虑到创建我们的默认DateFormatter 实例需要几行代码,我们可能不想在我们的属性声明列表中这样做--因为这可能最终使我们的代码难以阅读(特别是如果我们开始添加更多遵循相同模式的属性)。
解决这个问题的一个方法是让我们的formatter 属性变得懒惰,然后定义一个私有工厂方法来创建其默认值。这将使我们的属性声明保持 "无杂乱"--像这样:
class DateView: UIView {
var date = Date()
lazy var formatter = makeFormatter()
...
}
private extension DateView {
func makeFormatter() -> DateFormatter {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}
}
然而,虽然上述技术对类来说非常有效,但如果我们使用的是一个结构,会怎么样呢?例如,如果上面的DateView 是一个SwiftUI视图,而不是一个UIView 子类,那么使用懒惰属性就不起作用了,因为访问这样的属性会执行一个突变--这意味着我们无法在视图的body 中使用我们的formatter :
struct DateView: View {
var date = Date()
lazy var formatter = makeFormatter()
var body: some View {
VStack {
// Error: Cannot use mutating getter on immutable
// value: 'self' is immutable
Text(formatter.string(from: date))
...
}
}
}
private extension DateView {
func makeFormatter() -> DateFormatter {
...
}
}
当我们的视图以结构形式实现时,我们会得到上述编译器错误的原因是结构具有价值语义。要了解更多这方面的信息,请查看基础知识文章"值和引用类型"。
在这一点上,我们似乎需要回到我们最初使用的基于闭包的内联计算(因为这并不要求我们的属性是懒惰的),或者使用类似单子的东西。但事实证明还有另一种方法,那就是使用一个私有的静态方法来计算我们属性的默认值--就像这样:
struct DateView: View {
var date = Date()
var formatter = makeFormatter()
var body: some View {
...
}
}
private extension DateView {
static func makeFormatter() -> DateFormatter {
...
}
}
我们可以使用任何静态API(甚至是私有的)来计算一个属性的缺省值,这一事实也给了我们机会为我们所有的DateView 实例使用一个单一的、静态共享的DateFormatter ,在这种情况下,这可以提高我们UI的性能,因为我们不再在每次更新视图时重新创建一个新的日期格式化:
struct DateView: View {
var date = Date()
var formatter = defaultFormatter
var body: some View {
...
}
}
private extension DateView {
static let defaultFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}()
}
因此,懒惰属性和非懒惰属性之间的一个重要区别是,当计算懒惰属性的默认值时,我们是在该实例自己的上下文中,而当计算非懒惰属性的默认值时,我们是在一个静态上下文中。
当然,我们是应该共享一个静态实例还是创建多个静态实例,这要根据不同的情况而定。我的一般建议是只共享不可变的实例,或者在一个单一类型中私下共享,以避免引入全局可变状态。