问题描述
在尝试输出字符串首字母时,下面为示例代码:
var name = "Paul"
var firstLetter = name[name.startIndex]
var body: some View {
Text("\(firstLetter)")
}
发现存在下面的报错,
Cannot use instance member 'name' within property initializer; property initializers run before 'self' is available
翻译为:“无法在属性初始化程序中使用实例成员“name”;属性初始化程序在“self”可用之前运行。”
经排查发现,问题的原因在于firstLetter属性在初始化时
var firstLetter = name[name.startIndex]
试图访问name,但是name还没有被完全初始化。
可以简单的理解为,Swift不允许在初始化变量时,去引用另一个属性。
尽管name已经声明并分配了一个”Paul”的值。
解决方案
可通过以下两种方法来确保name完全初始化:
1、在init()方法中,初始化firstLetter变量。
struct ContentView: View {
var name = "Paul"
var firstLetter: Character
init() {
self.firstLetter = name[name.startIndex] // 在初始化方法中,name 已经被初始化,可以安全使用
}
var body: some View {
Text("\(firstLetter)")
}
}
init()方法是Swift初始化对象的核心步骤,当使用init方法时,你可以先初始化name,然后在name初始化完成后,安全的访问并计算firstLetter。
2、使用计算属性,每次访问时动态计算。
struct ContentView: View {
var name = "Paul"
var firstLetter: Character {
name[name.startIndex]
}
var body: some View {
Text("\(firstLetter)")
}
}
计算属性不会存储值,因为计算属性只有在被访问时,才会执行代码。
在Swift初始化完成之后,计算属性才会尝试访问其他存储属性,这也意味着它不会在self没有初始化完成时求值,避免了在初始化时访问其他属性的问题。
我们可以通过init()和计算属性,完成初始化时访问其他属性的问题,但是,我们可能还有一个疑问,就是这里的初始化操作全部放在init()中执行,是否可行?
下面就是对于init()方法的延续分析。
init()方法延续分析
struct ContentView: View {
var name: String
var firstLetter: Character
init() {
self.name = "Paul" // 先初始化 name
self.firstLetter = name[name.startIndex] // 然后再用 name 计算 firstLetter
}
var body: some View {
Text("\(firstLetter)")
}
}
当我们将name和firstLetter的初始化步骤全部放在init()中执行,就不会存在本次问题报错。
这是因为,如果你在没有使用init()的情况下,Swift在初始化属性时有一个严格的规则,就是在任何属性初始化之前,不能依赖为初始化的属性。
而init()的作用就是允许你按照你的顺序来初始化全部属性。
这里,需要了解一下init方法的特殊性,它允许逐步初始化属性,并且在方法当中,你可以确定某个属性被赋值,并依赖它进行下一步的计算。
问题总结
总而言之,就是当初始化使用init()时,我们可以明确控制属性的初始化顺序,并且可以依赖其他的初始化属性。
如果不使用init(),属性就不能保证初始化的顺序,以及不可以依赖其他的初始化属性。
这是Swift的安全机制,避免在属性未完全初始化时,进行不安全的引用。