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

时间:2017-11-21 13:39:18

标签: 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 property: 'b' is a 'let' constant
        self.init()
    }
}

3 个答案:

答案 0 :(得分:4)

我认为这与子类化有关,而与类初始化器有关。

错误只能说明问题的一半,将blet更改为var会显示此类的其他问题:

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

  • 便捷初始化程序必须先调用指定的初始化程序才能访问self

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

class Base1 {
    init() { }
}

class Sub1: Base1 {
    let b: Int

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

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

let one = Sub1(b: 10)
one.b   // prints 10
let two = Sub1()
two.b   // prints 5

答案 1 :(得分:1)

首先,imho,这与派生类没有任何关系,你可以删除子类并获得类似的错误。

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

class Sub1: Base1 {
    let b: Int

    // You must init b
    // Or ger error: property 'self.b' not initialized ...
   init(b: Int) {
        self.b = b
        super.init()
    }

    // Do you really need the code below now?
    //convenience init(b: Int) {
    //    self.b = b
    //    self.init()
    //}
}

因此,当指定的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()
}

现在想象一下你是“编译器”并告诉我常量b 在哪里被设置为常量值而是什么值?

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

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)
    }
}

所以,我担心的是,我不清楚你是否真的希望通过在easy init中初始化let b来实现什么?

答案 2 :(得分:1)

在Swift中,指定的初始值设定项用于设置所有存储的属性。这来自官方documentation

  

指定的初始值设定项是类的主要初始值设定项。一个   指定的初始化程序完全初始化所引入的所有属性   该类并调用适当的超类初始化程序继续   初始化过程超级链。

便捷初始化程序是次要的,需要调用已设定的初始化程序。

  

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

当您尝试代码时,虽然此时错误消息不明确,但问题是您无法使用便利init来初始化存储的属性。你必须使b可选var才能使它工作。

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)
    }
}