1. Lazy var in Swift explained with code examples
A lazy var 是一个直到第一次调用它时才计算其初始值的属性。它是属性家族的一部分,我们有常量属性、计算属性和可变属性。
在Swift中,一个惰性属性可能不太为初学者所知,但一旦你知道何时以及如何使用它们,它实际上是非常有价值的。有一些重要的事情需要了解,以便您知道何时使用哪种类型的属性。
延迟存储属性是指直到第一次使用它时才计算其初始值的属性。通过在修饰符声明之前编写惰性修饰符来声明惰性存储属性。
1.1. What is a lazy var?
“lazy”这个词最能说明惰性属性的含义,因为惰性属性的初始值直到第一次使用时才会计算出来。换句话说:这是延迟初始化。这可能是优化代码以避免做任何不必要工作的好方法。
惰性变量如下所示:
lazy var oldest: Person? = {
return people.max(by: { $0.age < $1.age })
}()
1.2. When should I use a lazy stored property?
对于昂贵且不太可能被一致调用的代码,惰性变量是一个很好的解决方案。让我们以上面的例子为例,在视图模型中使用它:
struct Person {
let name: String
let age: Int
}
struct PeopleViewModel {
let people: [Person]
lazy var oldest: Person? = {
print("Lazy var oldest initialized")
return people.max(by: { $0.age < $1.age })
}()
init(people: [Person]) {
self.people = people
print("View model initialized")
}
}
对一组元素排序的代价可能很高,所以我们希望确保只在实际使用值时才执行这个操作。因此,惰性变量是一个很好的解决方案。一旦实际计算出值,就会调用print语句:
var viewModel = PeopleViewModel(people: [
Person(name: "Antoine", age: 30),
Person(name: "Jaap", age: 3),
Person(name: "Lady", age: 3),
Person(name: "Maaike", age: 27)
])
// Prints: "View model initialized"
print(viewModel.oldest)
// Prints: "Lazy var oldest initialized"
// Prints: Person(name: "Antoine", age: 30)
如您所见,print语句不会在初始化视图模型本身时调用,而是在调用属性oldest之后才调用。
1.3. The difference between a computed and a lazy stored property
理解计算属性和延迟存储属性之间的区别很重要。 如果我们使用了computed属性,那么oldest的值每次都会重新计算:
struct PeopleViewModel {
let people: [Person]
var oldest: Person? {
print("oldest person calculated")
return people.max(by: { $0.age < $1.age })
}
}
print(viewModel.oldest)
// Prints: "oldest person calculated"
// Prints: Person(name: "Antoine", age: 30)
print(viewModel.oldest)
// Prints: "oldest person calculated"
// Prints: Person(name: "Antoine", age: 30)
与只调用一次的惰性变量相比,这是性能上的损失:
struct PeopleViewModel {
let people: [Person]
lazy var oldest: Person? = {
print("oldest person calculated")
return people.max(by: { $0.age < $1.age })
}()
}
print(viewModel.oldest)
// Prints: "oldest person calculated"
// Prints: Person(name: "Antoine", age: 30)
print(viewModel.oldest)
// Prints: Person(name: "Antoine", age: 30)
由于这种差异,理解惰性变量的生命周期很重要,因为它可能会带来意想不到的副作用。
1.4. Understanding the lifecycle of lazy variables
由于惰性属性只计算一次调用,这意味着它也将使用它被调用的时刻的状态。例如,如果人的集合是可变的,这可能意味着在发生突变之前调用的oldest是不同的:
struct PeopleViewModel {
var people: [Person]
lazy var oldest: Person? = {
print("oldest person calculated")
return people.max(by: { $0.age < $1.age })
}()
}
var viewModel = PeopleViewModel(people: [
Person(name: "Antoine", age: 30),
Person(name: "Jaap", age: 3),
Person(name: "Lady", age: 3),
Person(name: "Maaike", age: 27)
])
print(viewModel.oldest)
// Prints: "oldest person calculated"
// Prints: Person(name: "Antoine", age: 30)
viewModel.people.append(Person(name: "Jan", age: 69))
print(viewModel.oldest)
// Prints: Person(name: "Antoine", age: 30)
在将Jan加上69岁后,你会期望老人返回Jan。然而,由于最老的在突变发生之前已经初始化,存储的值被设置为Antoine,年龄为30岁。在这种情况下,使用计算属性可能会更好,因为它会考虑人员集合的实际状态。
1.5. Lazy stored properties are mutable
另一件需要意识到的事情是,惰性变量是可变的。这意味着你只能在可变结构上调用惰性变量:
let viewModel = PeopleViewModel(people: [
Person(name: "Antoine", age: 30),
Person(name: "Jaap", age: 3),
Person(name: "Lady", age: 3),
Person(name: "Maaike", age: 27)
])
print(viewModel.oldest)
如果你的结构不是可变的,而你正在调用惰性变量,你会遇到这样的错误:
Cannot use mutating getter on immutable value: ‘viewModel’ is a ‘let’ constant
这只适用于值类型,在本例中是结构体。如果我们的视图模型是一个类,这个错误就不会发生。
1.6. Conclusion
惰性变量允许延迟存储属性的初始化。这对于只在实际需要时执行昂贵的工作是有用的。在需要根据值的当前状态进行计算的情况下,惰性属性和计算属性之间的区别非常重要。