类未实现其超类的必需成员


155

因此,我今天更新到了Xcode 6 beta 5,并注意到我在Apple类的几乎所有子类中都收到了错误。

错误状态:

类“ x”未实现其超类的必需成员

这是我选择的一个示例,因为该类目前非常轻便,因此易于发布。

class InfoBar: SKSpriteNode  { //Error message here

    let team: Team
    let healthBar: SKSpriteNode

    init(team: Team, size: CGSize) {
        self.team = team
        if self.team == Team.TeamGood {
            healthBar = SKSpriteNode(color: UIColor.greenColor(), size:size)
        }
        else {
            healthBar = SKSpriteNode(color: UIColor.redColor(), size:size)
        }
        super.init(texture:nil, color: UIColor.darkGrayColor(), size: size)

        self.addChild(healthBar)

    }

}

所以我的问题是,为什么我会收到此错误,我该如何解决?我没有执行什么?我正在呼叫指定的初始化程序。

Answers:


127

来自开发人员论坛上的Apple员工:

“向编译器和生成的程序声明您确实不希望与NSCoding兼容的方法是执行以下操作:”

required init(coder: NSCoder) {
  fatalError("NSCoding not supported")
}

如果您知道自己不想遵守NSCoding,则可以选择这种方法。我已经在很多SpriteKit代码中采用了这种方法,因为我知道我不会从情节提要中加载它。


您可以采用的另一种效果很好的方法是将该方法实现为一种方便的初始化,如下所示:

convenience required init(coder: NSCoder) {
    self.init(stringParam: "", intParam: 5)
}

请注意中对初始化程序的调用self。与所有非可选属性相反,这使您只需要对参数使用伪值,同时避免引发致命错误。


第三个选项当然是在调用super时实现该方法,并初始化所有非可选属性。如果对象是从情节提要中加载的视图,则应采用以下方法:

required init(coder aDecoder: NSCoder!) {
    foo = "some string"
    bar = 9001

    super.init(coder: aDecoder)
}

3
不过,第二种选择在大多数现实生活中都是没有用的。以我需要的初始化器为例init(collection:MPMediaItemCollection)。您必须提供真实的媒体项目集合;这就是本课的重点。没有一个类,就无法实例化该类。将要分析集合并初始化许多实例变量。这就是唯一指定的初始值设定项的全部要点!因此,这里init(coder:)没有有意义的(甚至毫无意义的)MPMediaItemCollection可供提供;只有fatalError方法是正确的。
马特2014年

@matt正确,一个或另一个选项在不同情况下会更好。
本·凯恩2014年

是的,我确实独立地发现并考虑了第二种选择,有时这很有意义。例如,我可以声明我的di init(collection:MPMediaItemCollection!)。那将允许init(coder:)通过零。但是后来我意识到:不,现在您只是在欺骗编译器。传递nil是不可接受的,因此抛出fatalError并继续前进。:)
马特

1
我知道这个问题及其答案现在已经很老了,但是我发布了一个新答案,其中指出了一些我认为对于真正理解此错误至关重要的要点,而这些问题是任何现有答案都没有解决的。
nhgrif

好答案。我同意您的理解,Swift并不总是继承超级初始化程序,这对于理解这种模式至关重要。
本·凯恩

71

现有答案中缺少两个绝对关键的特定于Swift的信息,我认为这些信息可以彻底清除这些信息。

  1. 如果协议将初始化程序指定为必需方法,则必须使用Swift的required关键字标记该初始化程序。
  2. Swift有一组关于init方法的特殊继承规则。

文艺青年最爱的是这样的:

如果实现任何初始化程序,则不再继承任何超类的指定初始化程序。

您将继承的唯一的初始化器(如果有的话)是超类便捷初始化器,它们指向您碰巧要覆盖的指定初始化器。

那么...准备好长版本了吗?


Swift有一组关于init方法的特殊继承规则。

我知道这是我提出的两点中的第二点,但是我们无法理解第一点,或者为什么在required理解这一点之前甚至不存在关键字。一旦了解了这一点,另一点就很明显了。

我在此答案本节中涵盖的所有信息均来自Apple的文档,该文档位于此处

从Apple文档中:

与Objective-C中的子类不同,Swift子类默认情况下不会继承其超类初始化程序。Swift的方法可以防止这样的情况,即超类的简单初始化程序被更专门的子类继承,并用于创建未完全或正确初始化的子类的新实例。

强调我的。

因此,直接从Apple文档中可以看到,Swift子类将不会总是(而且通常不会)继承其超类的init方法。

那么,它们什么时候从其超类继承?

有两个规则定义子类何时init从其父类继承方法。从Apple文档中:

规则1

如果您的子类没有定义任何指定的初始化器,它将自动继承其所有超类指定的初始化器。

规则二

如果您的子类提供了其所有超类指定的初始化器的实现(通过按照规则1继承它们,或通过提供自定义实现作为其定义的一部分),那么它将自动继承所有超类便利性的初始化器。

第2条是不是特别相关的这次谈话,因为SKSpriteNodeinit(coder: NSCoder)不太可能是一个方便的方法。

因此,您的InfoBar类一直继承required初始化器,直到您添加为止init(team: Team, size: CGSize)

如果您没有提供此init方法,而是将您InfoBar添加的属性设为可选或为它们提供了默认值,那么您仍将继承SKSpriteNodeinit(coder: NSCoder)。但是,当我们添加自己的自定义初始化程序时,我们停止继承超类的指定初始化程序(以及不指向我们实现的初始化程序的便捷初始化程序)。

因此,作为一个简单的示例,我提出以下内容:

class Foo {
    var foo: String
    init(foo: String) {
        self.foo = foo
    }
}

class Bar: Foo {
    var bar: String
    init(foo: String, bar: String) {
        self.bar = bar
        super.init(foo: foo)
    }
}


let x = Bar(foo: "Foo")

出现以下错误:

呼叫中缺少参数'bar'的参数。

在此处输入图片说明

如果这是Objective-C,则继承不会有问题。如果我们在Objective-C中Bar使用初始化a initWithFoo:,则该self.bar属性将仅仅是nil。它可能不是很大,但它是一个非常有效的对象是在状态,它是为斯威夫特对象是在一个完全有效的状态, self.bar是不是可有可无,不能nil

同样,我们继承初始化程序的唯一方法是不提供自己的初始化程序。因此,如果我们尝试通过删除Bar的来继承init(foo: String, bar: String),例如:

class Bar: Foo {
    var bar: String
}

现在我们回到继承(之类),但这将无法编译...并且错误消息确切解释了为什么我们不继承超类init方法:

问题: “ Bar”类没有初始化程序

Fix-It:没有初始化程序的存储属性“ bar”会阻止合成初始化程序

如果我们在子类中添加了存储的属性,则无法通过Swift方法使用超类初始化器创建子类的有效实例,而这些初始化器可能无法知道子类的存储属性。


好吧,那我为什么必须实施init(coder: NSCoder)呢?为什么呢required

Swift的init方法可能遵循一组特殊的继承规则,但是协议一致性仍然沿链继承。如果父类符合协议,则其子类必须符合该协议。

通常,这不是问题,因为大多数协议只需要Swift中特殊的继承规则无法使用的方法,因此,如果您要从符合协议的类中继承,那么您还将继承所有允许类满足协议一致性的方法或属性。

但是,请记住,Swift的init方法遵循一组特殊的规则,并且并不总是继承的。因此,符合要求特殊init方法(例如NSCoding)的协议的类要求该类将这些init方法标记为required

考虑以下示例:

protocol InitProtocol {
    init(foo: Int)
}

class ConformingClass: InitProtocol {
    var foo: Int
    init(foo: Int) {
        self.foo = foo
    }
}

这不会编译。它生成以下警告:

问题:初始化程序要求“ init(foo :)”只能由非最终类“ ConformingClass”中的“必需”初始化程序来满足

Fix-It:需要插入

它要我使init(foo: Int)初始化程序成为必需。我也可以通过创建类使它感到高兴final(这意味着该类不能继承)。

那么,如果我继承了子类会怎样?从这一点来说,如果我继承了,我很好。如果我添加任何初始化程序,我将突然不再继承init(foo:)。这是有问题的,因为现在我不再遵守InitProtocol。我不能从符合协议的类中继承子类,然后突然决定我不再要符合该协议。我已经继承了协议一致性,但是由于Swift与init方法继承一起工作的方式,我没有继承该协议所需要的部分内容,因此我必须实现它。


好吧,这一切都说得通。但是,为什么我无法获得更有用的错误消息?

可以说,如果错误消息指定您的类不再符合继承的NSCoding协议并且需要对其进行修复,则错误消息可能会更清晰或更佳init(coder: NSCoder)。当然。

但是Xcode根本无法生成该消息,因为如果不实现或继承所需的方法,这实际上永远不会是实际的问题。除了协议一致性之外,还有其他至少一个原因来制定init方法required,这就是工厂方法。

如果要编写适当的工厂方法,则需要将返回类型指定为Self(Swift等同于Objective-C的instanceType)。但是,为此,我实际上需要使用required初始化方法。

class Box {
    var size: CGSize
    init(size: CGSize) {
        self.size = size
    }

    class func factory() -> Self {
        return self.init(size: CGSizeZero)
    }
}

这会产生错误:

使用元类型值构造“ Self”类类型的对象必须使用“ required”初始化程序

在此处输入图片说明

这基本上是相同的问题。如果我们子类化Box,则我们的子类将继承类方法factory。这样我们可以打电话给SubclassedBox.factory()。然而,如果没有required对关键字init(size:)的方法,Box的子类都不能保证继承self.init(size:)factory被调用。

因此,required如果我们需要这样的工厂方法,就必须使用该方法,这意味着如果我们的类实现了这样的方法,我们将有一个required初始化方法,并且将遇到与在此遇到的完全相同的问题与NSCoding协议。


最终,所有这些都归结为基本理解,即Swift的初始化程序通过一组稍有不同的继承规则发挥作用,这意味着您不能保证从父类继承初始化程序。发生这种情况是因为超类初始化程序无法了解您的新存储属性,并且无法将您的对象实例化为有效状态。但是,由于各种原因,超类可能会将初始化器标记为required。当这样做时,我们可以采用一种非常具体的方案来实际继承该required方法,也可以自己实现。

但是,这里的主要要点是,如果我们收到您在此处看到的错误,则意味着您的类实际上根本没有实现该方法。

也许可以作为一个最后的例子来说明一个事实,那就是Swift子类并不总是继承其父init方法(我认为这对于充分理解此问题绝对至关重要),请考虑以下示例:

class Foo {
    init(a: Int, b: Int, c: Int) {
        // do nothing
    }
}

class Bar: Foo {
    init(string: String) {
        super.init(a: 0, b: 1, c: 2)
        // do more nothing
    }
}

let f = Foo(a: 0, b: 1, c: 2)
let b = Bar(a: 0, b: 1, c: 2)

这无法编译。

在此处输入图片说明

它给出的错误消息有点误导性:

通话中额外的参数“ b”

但问题是,Bar没有继承任何的Fooinit,因为它没有任何的两种特殊情况下满足了继承方法init从它的父类的方法。

如果这是Objective-C,我们将init毫无问题地继承它,因为Objective-C非常满意不初始化对象的属性(尽管作为开发人员,您不应该对此感到满意)。在Swift中,这根本不会做。您不能有无效的状态,继承超类初始化程序只能导致无效的对象状态。


您能否解释一下这句话的意思或举一个例子?“(和方便的初始化程序,它们并不指向我们实现的初始化程序)”
Abbey Jackson

辉煌的答案!我希望有更多关于此类的理由,而不是仅仅如此
亚历山大·瓦塞宁

56

为什么会出现这个问题?那么,显而易见的事实是,它总是很重要(即在Objective-C,因为那天我开始编程可可回在Mac OS X 10.0)来处理初始化你的类没有准备好处理。对于您在这方面的责任,文档一直很清楚。但是,我们当中有多少人愿意完全按照书信来履行它们呢?也许我们俩都不是!而且编译器没有强制执行它们。这全是纯常规的。

例如,在我的Objective-C视图控制器子类中,此指定的初始化程序是:

- (instancetype) initWithCollection: (MPMediaItemCollection*) coll;

……至关重要的是,我们必须传递一个实际的媒体项目集合:没有一个实例就无法存在。但是我没有写任何“停止器”来阻止有人用准系统初始化我init。我应该已经编写了一个(实际上,正确地说,我应该已经编写initWithNibName:bundle:了继承的指定初始化程序的实现);但是我懒得打扰,因为我“知道”我永远不会那样错误地初始化自己的类。这留下了一个大漏洞。在Objective-C中,有人可以称呼“准系统” init,而我的ivars尚未初始化,而我们在没有桨的情况下处于小河中。

斯威夫特(Swift)很棒,在大多数情况下,使我从自己中解救出来。我将这个应用程序翻译成Swift后,整个问题就消失了。斯威夫特有效地为我制造了一个塞子!如果init(collection:MPMediaItemCollection)是在我的课程中声明的唯一指定的初始化方法,则无法通过调用raw-bones进行初始化init()。这是一个奇迹!

种子5中发生的事情仅仅是编译器意识到在的情况下该奇迹是行不通的init(coder:),因为从理论上讲此类的实例可能来自笔尖,并且编译器无法阻止这种情况-并且当笔尖负载,init(coder:)将被调用。因此,编译器使您可以显式地编写停止器。也很正确。


感谢您提供如此详细的答案。这确实给事情带来了曙光。
朱利安·奥索里奥

对pasta12表示赞成,告诉我如何使编译器关闭,但对您也表示赞成,因为它使我了解了它最初的想法。
加勒特·奥尔布赖特2014年

2
不管是否有漏洞,我永远都不会将其称为init,因此强迫我包含它是完全不合理的。膨胀的代码是我们不需要的开销。现在,它也迫使您同时初始化两个属性。无意义!
丹·格林菲尔德

5
@DanGreenfield不,它不会强制您初始化任何东西,因为如果您永远不愿调用它,只需放入stackoverflow.com/a/25128815/341994中所述的fatalError止动器即可。只需将其作为用户代码段即可,从现在开始,您可以将其放入需要的位置。需要半秒钟。
哑光2014年

1
@nhgrif好吧,公平地说,这个问题并不需要完整的故事。这只是关于如何摆脱困境并继续前进。我的书中提供了完整的故事:apeth.com/swiftBook/ch04.html#_class_initializers
matt

33

required init(coder aDecoder: NSCoder!) {
  super.init(coder: aDecoder)
}

3
这确实有效,但我认为这不是错误。初始化器不会快速继承(声明了您自己的初始化器时),并且使用必需的关键字进行了标记。唯一的问题是,现在我需要为我的每个类初始化此方法中的所有属性,这将是很多浪费的代码,因为我根本不使用它。或者,我将必须将所有属性声明为隐式解包的可选类型,以绕过我也不想执行的初始化。
史诗字节

1
对!在说出可能是一个错误之后,我意识到这实际上是合乎逻辑的。我同意这会浪费很多代码,因为像您一样,我永远不会使用此init方法。尚不确定是否有一个优雅的解决方案
Gagan Singh 2014年

2
我遇到过同样的问题。对于“ required init”来说,这是有道理的,但是迅速并不是我所希望的“简单”语言。所有这些“可选”使语言比所需的更加复杂。并且不支持DSL和AOP。我越来越失望了。
user810395 2014年

2
是的,我完全同意。我的许多属性现在都声明为可选属性,因为我被迫这样做,而实际上不应该将它们设为nil。有些是可选的,因为合法地它们应该是可选的(意味着nil是有效值)。然后在不进行子类化的类中,我不需要使用可选类,因此事情变得非常复杂,而且我似乎找不到合适的编码样式。希望苹果能弄清楚一些事情。
史诗字节

5
我认为这意味着您可以通过声明自己的任何初始化程序来满足所需的初始化程序,这将导致所有初始化程序都被继承。
史诗字节
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.