类没有实现其超类所需的成员

所以我今天更新了Xcode 6 beta 5,注意到我几乎所有的苹果类的子类都收到了错误。

错误状态:

类“x”没有实现其超类所需的成员

这里是我select的一个例子,因为这个类目前很轻,所以很容易发布。

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

所以我的问题是,为什么我收到这个错误,我该如何解决? 那我没有执行什么? 我正在调用一个指定的初始化程序。

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

“向编译器和构build的程序声明你真的不想与NSCoding兼容的一种方法就是做这样的事情:”

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

如果你知道你不想成为NSCoding兼容,这是一个选项。 我已经采取了很多我的SpriteKit代码的方法,因为我知道我不会从故事板加载它。


你可以采取的另一种方法是将方法作为一个便利的init来实现,就像这样:

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

注意调用自己的初始化器。 这允许您只需要使用参数的虚拟值,而不是所有非可选属性,同时避免引发致命错误。


当然,第三个选项是在调用super时实现该方法,并初始化所有的非可选属性。 如果对象是从故事板加载的视图,则应该采取这种方法:

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

有两个绝对至关重要的Swift特定信息片断,我认为这些信息已经完全清除了。

  1. 如果一个协议指定一个初始化器作为必需的方法,那么这个初始化器必须使用Swift的required关键字进行标记。
  2. Swift有一套特殊的关于init方法的inheritance规则。

tl; dr是这样的:

如果你实现了任何初始化器,你将不再inheritance任何超类的指定初始化器。

你将inheritance的唯一的初始化器,如果有的话,是超类的便利初始化器,指向一个指定的初始化器,你碰巧覆盖。

那么…准备好长版本?


Swift有一套特殊的关于init方法的inheritance规则。

我知道这是我提出的两点,但是我们无法理解第一点,或者为什么required关键字甚至存在,直到我们理解了这一点。 一旦我们理解了这一点,另一个就变得非常明显了。

我在这个答案的这一部分所涵盖的所有信息都来自苹果在这里find的文档。

从苹果文档:

与Objective-C中的子类不同,Swift子类默认不会inheritance它们的父类初始化程序。 Swift的方法避免了一个情况,即一个超类的简单初始值设定项被一个更专门化的子类inheritance,并被用来创build一个未完全或正确初始化的新子类。

强调我的。

所以,直接从苹果文档,我们看到,Swift子类不会总是(通常不)inheritance它们的父类的init方法。

那么,他们什么时候从他们的超类inheritance?

有两个规则定义子类何时从其父类inheritanceinit方法。 从苹果文档:

规则1

如果你的子类没有定义任何指定的初始值设定项,它将自动inheritance它的所有超类指定的初始值设定项。

规则2

如果你的子类提供了它的所有超类指定的初始化符的实现,或者按照规则1inheritance它们,或者提供一个自定义实现作为其定义的一部分,那么它将自动inheritance所有的超类方便初始化符。

规则2与这个对话不是特别相关,因为SKSpriteNodeinit(coder: NSCoder)不太可能是一个方便的方法。

所以,你的InfoBar类inheritance了required初始化器,直到你添加了init(team: Team, size: CGSize)

如果你没有提供这个init方法,而是使你的InfoBar的附加属性可选或者提供了默认值,那么你仍然inheritanceSKSpriteNodeinit(coder: NSCoder) 。 然而,当我们添加了我们自己的自定义初始值设定项时,我们停止了inheritance我们的超类的指定初始值设定项(以及没有指向我们实现的初始值设定项的便利初始值设定项)。

所以,作为一个简单的例子,我提出这个:

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

其中出现以下错误:

在调用中缺less参数“bar”的参数。

在这里输入图像描述

如果这是Objective-C,那么inheritance就没有问题了。 如果我们用initWithFoo:初始化一个Bar initWithFoo:在Objective-C中, self.bar属性就是nil 。 这可能不是很好,但它是一个完全有效的状态,它不是一个完全有效的Swift对象的状态self.bar不是一个可选的,不能nil

同样,我们inheritance初始化的唯一方法是不提供我们自己的。 所以如果我们试图通过删除Barinit(foo: String, bar: String)来inheritance,就像这样:

 class Bar: Foo { var bar: String } 

现在我们回到inheritance(有点),但是这不会编译…而错误消息正好解释了为什么我们不能inheritance超类init方法:

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

修复它:存储属性“酒吧”没有初始化器防止合成的初始化

如果我们已经在子类中添加了存储的属性,那么就不可能用Swift的方法来创build一个我们的子类的有效实例,并且使用超类的初始化方法,这些方法不可能知道我们子类的存储属性。


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

Swift的init方法可能会由一组特殊的inheritance规则来发挥作用,但协议一致性仍然沿着链条inheritance下来。 如果父类符合协议,则其子类必须符合该协议。

通常情况下,这不是问题,因为大多数协议只需要在Swift中不使用特殊inheritance规则的方法,所以如果你从一个符合协议的类inheritance,那么你也inheritance了所有的方法或属性,允许类满足协议一致性。

但是请记住,Swift的init方法是由一组特殊的规则来执行的,并不总是被inheritance的。 因此,符合需要特殊init方法(如NSCoding )的协议的类要求该类根据required标记这些init方法。

考虑这个例子:

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

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

问题:初始化器需求'init(foo :)'只能通过非final类中的'required'初始化器'ConformingClass'

修复它:插入必需

它希望我使init(foo: Int)初始值设定项成为必需。 我也可以通过让类final (意味着类不能被inheritance)。

那么,如果我inheritance,会发生什么? 从这一点来说,如果我inheritance,我没事。 如果我添加任何初始化器,我突然不再inheritanceinit(foo:) 。 这是有问题的,因为现在我不再符合InitProtocol 。 我不能从一个符合协议的类inheritance,然后突然决定我不再想遵守这个协议。 我inheritance了协议一致性,但由于Swift使用init方法inheritance的方式,我没有inheritance遵循该协议所需的部分内容,所以我必须实现它。


好的,这一切都是有道理的。 但为什么我不能得到更有帮助的错误信息?

可以说,错误信息可能会更清楚或更好,如果它指定您的类不再符合inheritanceNSCoding协议,并修复它需要实现init(coder: NSCoder) 。 当然。

但Xcode根本无法生成该消息,因为实际上并不总是实现或inheritance所需方法的实际问题。 除了协议一致性之外,至less还有另外一个原因需要使用init方法,那就是工厂方法。

如果我想写一个适当的工厂方法,我需要指定返回types为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) } } 

这会产生错误:

使用元types值构造类types为“Self”的对象必须使用“必需的”初始值设定项

在这里输入图像描述

这基本上是一样的问题。 如果我们inheritanceBox ,我们的子类将inheritance类方法factory 。 所以我们可以调用SubclassedBox.factory() 。 但是,如果init(size:)方法没有required关键字,则Box的子类不能保证inheritancefactory正在调用的self.init(size:)

所以如果我们需要像这样的工厂方法,那么我们必须使用这个方法,这意味着如果我们的类实现了这样的方法,我们将有一个required初始化方法,我们将遇到与您运行完全相同的问题到NSCoding协议到这里。


最终,这一切都归结为基本的理解,即Swift的初始化器发挥一点点不同的inheritance规则,这意味着你不能保证从你的超类inheritance初始化器。 发生这种情况是因为超类初始化程序无法知道您的新存储属性,并且无法将您的对象实例化为有效状态。 但是,由于各种原因,超类可能需要标记一个初始化器。 如果是这样,我们可以采用其中一种我们实际上inheritance了required方法的特定scheme,或者我们必须自己实施。

这里的主要观点是,如果我们在这里看到错误,这意味着你的类没有真正实现这个方法。

作为最后一个例子,我们可以深入理解Swift子类并不总是inheritance父类的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不会inheritanceFoo的任何init方法,因为它没有满足从父类inheritanceinit方法的两种特殊情况。

如果这是Objective-C,我们会inheritance这个init ,没有任何问题,因为Objective-C非常高兴不初始化对象的属性(尽pipe作为开发者,你不应该对此感到满意)。 在Swift中,这根本就不行。 你不能有一个无效的状态,inheritance超类初始化只能导致无效的对象状态。

为什么会出现这个问题? 那么,显而易见的事实是,它始终是非常重要的(即在Objective-C中,从我开始将Cocoa编程回Mac OS X 10.0的那一天起)来处理你的类没有准备处理的初始化器。 文件一直很清楚你在这方面的责任。 但是我们有多less人为了完成这封信而烦恼呢? 大概没有人! 编译器没有强制执行它们; 这完全是传统的。

例如,在我指定的初始值设定项的Objective-C视图控制器子类中:

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

……我们必须通过一个实际的媒体项目集合:这个实例根本就不能没有一个。 但是我没有写“塞子”来阻止某人用简单的init来初始化我。 我应该写一个(实际上,正确地说,我应该写了一个initWithNibName:bundle:的实现,inheritance的指定初始值设定项)。 但是我懒得打扰,因为我“知道”我永远不会以这种方式错误地初始化我自己的类。 这留下了一个大洞。 在Objective-C中,有人可以调用简单的init ,让我的ivars不被初始化,而我们没有一个桨就在小河中。

奇妙的是,斯威夫特在大多数情况下都能救我。 只要我把这个应用程序翻译成Swift,整个问题就消失了。 Swift有效地为我创造了一个塞子! 如果init(collection:MPMediaItemCollection)是我的类中声明的唯一指定的初始化方法,则不能通过调用bare-bones init()来初始化。 这是一个奇迹!

种子5中发生的事情仅仅是编译器意识到这个奇迹在init(coder:)的情况下不起作用,因为理论上这个类的一个实例可能来自一个nib,而编译器不能阻止那 – 当nib加载的时候, init(coder:)会被调用。 所以编译器使你明确地写死锁。 而且很对。

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