一个Swift子类是否总是需要调用super.init()?

35
如果我有一个Swift子类:
  1. 不需要访问 self
  2. 不需要访问 self 上的任何属性
那么在子类的 init() 方法中,我是否仍然需要调用 super.init()
*注意:这是一个不同于此处在SO上问答的问题,因为它指定了上述两个细节。
4个回答

31

不,你不必这样做。

假设你有以下的类。

class a {
    let name: String

    init() {
        name = "james"
    }
}

class b: a {
    let title: String

    override init() {
        title = "supervisor"
    }
}
如果你使用以下语句实例化一个变量: let myVar = b() 那么:
- 在b中,override init()将被调用。 - 然后在a中调用init()。 即使你没有显式调用super.init()
这已经被Chris Laettner在swift-user的电子邮件列表中确认。当你的父类有一个具有零参数init的单一指定初始化程序时,它会发挥作用。这就是为什么从NSObject派生不必调用super.init()的原因。
*感谢Wingzero下面的评论

1
不,Swift的初始化与Objective-C的初始化不同。现在不再是在执行初始化之前调用[super init]的标准做法了。https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html - bearMountain
从那个页面上看,“一旦超类的指定初始化程序完成,子类的指定初始化程序可以执行其他自定义操作(尽管它不必这样做)。" - Joe
你可以将其视为一个三步骤过程:(1)初始化子类的新实例变量,(2)调用super.init,(3)任何进一步的活动(可以包括方法调用等)。如果您不需要第三步,Swift可以为您处理第二步。 - Nate Cook
1
@newacct 请参考上面 Viktor Lexington 的答案获取确切引用。我的回答更注重实际应用。基本上,super init 将被调用。如果您没有 init,则会继承。如果您有 init,则要么在其中自己调用 super,要么它将在您的 init 结束时自动调用。 - Rikkles
2
Swift之父证实:https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20160516/001930.html:这是有意为之的行为。当您的超类具有单个指定的初始化器并带有零参数init时,它就会启动。这就是为什么从NSObject派生时不必调用super.init()的原因。 - Wingzero
显示剩余4条评论

13

根据文档:

指定的初始化器必须调用其直接超类的指定初始化器。

另外,关于自动初始化器继承:

假设你为子类中引入的任何新属性提供默认值,则以下两个规则适用:

规则1 如果你的子类没有定义任何指定初始化器,则它会自动继承其所有超类的指定初始化器。

规则2 如果你的子类提供了其所有超类指定初始化器的实现——如根据规则1继承它们或作为其定义的一部分提供自定义实现——则它会自动继承其所有超类的便利初始化器。

即使你的子类添加了更多的便利初始化器,这些规则也适用。

因此,回答你的问题如下:

你的子类将始终调用其超类的指定初始化器。如果你不编写初始化器,并且编译器没有抱怨,则它已经使用了自动初始化器继承。如果你编写了初始化器但未显式调用相关的上游(或旁路)初始化器,则在初始化器结束时,它将自动执行。

此外,链接初始化器的工作方式是一个两阶段的过程。在第一阶段中,它从子类开始向超类分配默认值。在第二阶段中,该过程反向运行,从超类开始,最终回到你的子类,其中对参数进行自定义和覆盖。

这意味着每个init()都必须首先设置变量,然后才能调用(或不调用)super.init()并运行自定义代码。所以,如果你想让超类的初始化器在开头运行,那么请考虑“开头”就是在创建变量之后:

class a {
    var name: String

    init() {
        name = "james"
        println("a")
    }
}

class b: a {
    let title: String

    override init() {
        title = "supervisor"
        super.init()
        self.name = "john"
        println("b")
    }
}

let x = b()

这将打印出a,然后是b。


2
如果你没有自己实现,编译器也没有报错,那么它就使用了自动初始化继承。自动初始化继承是指在没有指定初始化程序的情况下继承指定初始化程序,或者在覆盖了所有超类的指定初始化程序时继承便利初始化程序。我不明白这与隐式调用超类初始化程序有什么关系。 - newacct
规则1:如果您的子类没有定义任何指定的初始化程序,则它会自动继承其超类的所有指定的初始化程序。我的意思是,您无法避免调用超类的初始化程序。这就是所问的问题。 - Rikkles
我明白你的意思了,我刚刚更新了我的答案并加入了更具体的解释。谢谢。 - Rikkles
2
我理解“指定初始化器必须从其直接超类调用指定初始化器”是指不能不显式地调用超类的指定初始化器。“如果你编写了一个初始化器,但它没有显式地调用相关的上游(或侧流)初始化器,那么在你的初始化器结束时,这个过程将自动完成。” 我没有看到这方面的文档记录。 - newacct

5

是的,指定的初始化程序需要向上委托,因此必须调用 super.init()。 如果您不编写这行代码,则编译器将为您完成。

因此,即使仅在幕后,子类也必须显式地编写super.init(...)

但请记住,在第一阶段中,您需要在子类中设置属性,然后必须调用super.init()。 在第二阶段中,可以更改所有继承的属性。 我认为super.init()是第1阶段和第2阶段之间的完美分隔符。

从文档中获得:

安全检查2

在将值分配给继承的属性之前,指定的初始化程序必须向上委托到超类初始化程序。如果没有这样做,指定的初始化程序分配的新值将被超类覆盖,并成为其自身初始化的一部分。


1
我认为那句话不相关。从我的阅读理解来看,“it”指的是“在给继承属性赋值之前”。因此,“If it doesn't”=“如果你在调用超类初始化程序之前给继承属性赋值”,这是完全有道理的,因为超类初始化程序会覆盖它。但这与不调用超类初始化程序无关。 - newacct
@newacct 引用文本中第二个粗体行的想法是,如果您不写super.init() 我认为编译器在设置所有属性之后会自动设置它,这意味着您继承的属性都不会使用新值进行设置。为了确保一切正常,我会在设置添加的属性和设置继承的属性之间自己编写super.init()。(我没有进行广泛尝试,所以也许编译器在这方面是完美的) - Binarian

0

我认为你不必调用super。我尝试模拟以下情况:

class Animal {
}

class Parrot: Animal {

    override init() {
        // Some code (super not called)
    }
}

不过,我建议在实际应用中始终调用 super.init,就像您在 Objective-C 中所做的那样。


如果您没有显式调用 super.init(),它将在子类的 init() 结束时自动调用。您可以通过在基类中添加 init() { fatalError() } 来验证这一点。 - Andres Canella

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接