Swift 无法在派生类的方便初始值设定项中初始化常量属性

Swift 无法在派生类的方便初始值设定项中初始化常量属性,swift,initialization,subclass,Swift,Initialization,Subclass,下面是一个操场的示例代码。我不明白的是为什么子类中的b变量必须是var类型而不能是let。有人能帮我理解吗 class Base1 { init() { } } class Sub1: Base1 { let b: Int override init() { super.init() } convenience init(b: Int) { self.b = b // Cannot assign to propert

下面是一个操场的示例代码。我不明白的是为什么子类中的
b
变量必须是var类型而不能是let。有人能帮我理解吗

class Base1 {
    init() { }
}

class Sub1: Base1 {
    let b: Int

    override init() {
        super.init()
    }

    convenience init(b: Int) {
        self.b = b  // Cannot assign to property: 'b' is a 'let' constant
        self.init()
    }
}

我认为这与子类化关系不大,而与类初始值设定项关系更大

错误只说明了一半,将
b
let
更改为
var
将显示该类的其他问题:

  • 初始化者必须为类的每个存储属性设置初始值。如果覆盖
    init
    ,则必须为
    b
    提供默认值

  • 方便的初始值设定项必须在访问
    self

这就是它的外观(感谢Martin R在关于如何将
b
保持为
let
常量的评论中提出的改进):


首先,imho,这与派生类无关,您可以删除子类化并得到类似的错误

第二,我认为这是由于便利初始值设定项的“性质”及其使用。它们是“可选”(在使用中)的初始值设定项,例如提供从某些复杂数据(如其他类或结构)初始化的快捷方式。在示例中将
self.b
的初始化添加到便利init不会带来任何帮助,因为您无论如何都需要在指定的init中初始化
b

因此,由于指定的init必须初始化
b
,因此您编写的方便初始化器变得毫无意义

第三,假设这是允许的。因此,由于方便,初始值设定项被委托给其他人,因此不必调用指定的初始值设定项。因此,可以这样做:

...

convenience init(b: Int) {
    self.b = b
    self.init(c: 10)
}

// later some crazy guy adds his own "convenience" initializer and points your to this one.
convenience init(c: Int) {
    self.c = c
    if c == 7 {
       self.b = 11
    }
    self.init()
}
class Base1 {
    init() {}
}


class Sub1: Base1 {
    let b: Int

    init(b: Int) {
        self.b = b
        super.init()
    }

    // Convenience initializer providing default value
    override convenience init() {
        self.init(b: 7)
    }
}
现在假设你是“编译器”,告诉我常量
b
设置在哪里,它的常量值是多少

最后,根据我的理解,正确的用法可能是:

...

convenience init(b: Int) {
    self.b = b
    self.init(c: 10)
}

// later some crazy guy adds his own "convenience" initializer and points your to this one.
convenience init(c: Int) {
    self.c = c
    if c == 7 {
       self.b = 11
    }
    self.init()
}
class Base1 {
    init() {}
}


class Sub1: Base1 {
    let b: Int

    init(b: Int) {
        self.b = b
        super.init()
    }

    // Convenience initializer providing default value
    override convenience init() {
        self.init(b: 7)
    }
}

因此,我担心的是,我不清楚您真正想要通过允许在便利初始化中初始化b来实现什么,在Swift中,使用指定的初始值设定项来设置所有存储的属性。这是官方的消息:

指定的初始值设定项是类的主要初始值设定项。A. 指定的初始值设定项完全初始化由 并调用相应的超类初始值设定项以继续 超类链上的初始化过程

便利初始值设定项是次要的,需要调用去屏蔽初始值设定项

您还可以定义一个方便的初始值设定项来创建的实例 该类用于特定用例或输入值类型。只要公共初始化模式的快捷方式可以节省时间或使类的初始化更清晰,就可以创建方便的初始值设定项

当您尝试代码时,尽管此时错误消息不清楚,但问题是您无法使用便利初始化来初始化存储属性。您必须使b成为可选变量才能使其工作

class Sub1: Base1 {
    var b: Int?

    override init() {
        super.init()
    }

    convenience init(b: Int) {
        self.init()
        self.b = b
    }
}
或者更好的方法是,正如@Martin R在其中一条评论中所建议的,创建另一个指定的初始值设定项,并在重写init之前使用便利性,以提供默认值:

class Sub1: Base1 {
    let b: Int

    init(b: Int) {
        self.b = b
        super.init()
    }

    convenience override init() {
        self.init(b:5)
    }
}

更好的方法是:使
init(b:Int)
成为指定的初始值设定项,
init()
成为一个方便的初始值设定项(它调用
self.init(b:5)
)然后你可以将
b
保持为常量。这样更好,你是想将其作为自己答案的一部分添加,还是我应该将其编辑到我的答案中?实际上,便利初始值设定项是不需要的,因为您可以在指定的参数中有一个默认值,但您完全正确。实际上您只犯了一个错误,在
init(b:Int)
中使用了
便利
,而不是
覆盖init()
根据文档,存储属性应在指定的初始化器中初始化:)Wow。谢谢你的解释。