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

因此,我今天更新到 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)


}


}

所以我的问题是,为什么我会收到这个错误,我该如何修复它?我没有实施的是什么?我要调用一个指定的初始化程序。

50198 次浏览

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

一位苹果员工在开发者论坛上说:

”向编译器和构建的程序声明 不想与 NSCoding 兼容就是做这样的事情:

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

如果你知道你不想遵守 NSCoding,这是一个选择。我已经在我的很多 SpriteKit 代码中采用了这种方法,因为我知道我不会从故事板中加载它。


您可以采用的另一个工作得相当不错的选项是将该方法实现为一个方便的 init,如下所示:

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

为什么会出现这个问题?事实上,一直都是非常重要(比如在 Objective-C 中,从我开始在 Mac OS X 10.0中编写 Cocoa 的那一天起) ,它可以处理类没有准备好处理的初始化器。医生一直很清楚你在这方面的责任。但是,我们当中有多少人费心去完全和严格地履行它们呢?也许我们都不会!编译器并没有强制执行它们; 它们都是纯粹的常规。

例如,在我的 Objective-C 视图控制器子类中使用这个指定的初始化器:

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

... 传递给我们一个实际的媒体项集合是至关重要的: 没有这个实例就不可能存在。但我没有写“塞子”,以防止有人初始化我与骨干 init代替。我写了一个 应该(实际上,准确地说,我应该写一个 initWithNibName:bundle:的实现,继承的指定初始化程序) ; 但我懒得去烦恼,因为我“知道”我永远不会错误地初始化我自己的类。这留下了一个大洞。在 Objective-C 中,有人将 可以调用为最基本的 init,使得我的 IVAR 没有初始化,我们就像没有桨的小溪。

大多数情况下,迅捷,奇妙,拯救了我自己。我一把这个应用翻译成 Swift 整个问题就解决了。斯威夫特有效地为我创造了一个塞子!如果 init(collection:MPMediaItemCollection)是在我的类中声明的唯一指定的初始化器,我就不能通过调用基本的 init()来进行初始化。真是个奇迹!

种子5中发生的只是编译器已经意识到这个奇迹在 init(coder:)中不起作用,因为理论上这个类的一个实例可能来自一个 nib,而编译器不能阻止这一点ーー当 nib 加载时,init(coder:)将被调用。所以编译器让你明确地写出塞子。也很对。

有两个绝对 至关重要片段的斯威夫特特定的信息,是从现有的答案缺失,我认为有助于这一点完全清楚。

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

博士是这样的:

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

您将继承的唯一初始化器(如果有的话)是超类方便初始化器,它指向您碰巧重写的指定初始化器。

准备好听长版了吗?


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

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

在这个答案的这一部分中,我所涵盖的所有信息都来自于苹果公司的 给你文档。

来自苹果公司的文档:

与 Objective-C 中的子类不同,Swift 子类默认情况下不继承它们的超类初始化器。Swift 的方法防止了这样一种情况,即来自超类的简单初始化器被更专门化的子类继承,并用于创建未完全或正确初始化的子类的新实例。

重点是我的。

所以,直接从 Apple 文档中,我们可以看到 Swift 子类并不总是(通常也不会)继承它们的父类的 init方法。

那么,他们什么时候继承他们的超类呢?

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

规则一

如果您的子类没有定义任何指定的初始值设定项,它会自动继承它的所有超类指定的初始值设定项。

第二条

如果你的子类提供了它所有的超类指定初始化器的实现ーー要么按照规则1继承它们,要么作为定义的一部分提供一个自定义实现ーー那么它就会自动继承所有的超类方便初始化器。

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

因此,在添加 init(team: Team, size: CGSize)之前,InfoBar类一直在继承 required初始值设定项。

如果您没有提供这个 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’的参数。

enter image description here

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

同样,我们继承初始化器的唯一方法是不提供我们自己的初始化器。因此,如果我们试图通过删除 Barinit(foo: String, bar: String)来继承,比如:

class Bar: Foo {
var bar: String
}

现在我们回到继承(某种程度上) ,但是这不会编译... ... 错误消息解释了为什么我们不继承超类 init方法:

问题: 类‘ Bar’没有初始化器

修复: 没有初始值设定项的存储属性‘ 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”的对象必须使用“必需”初始值设定项

enter image description here

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

所以如果我们想要一个这样的工厂方法我们必须创建这个方法 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)

编译失败。

enter image description here

它给出的错误信息有点误导:

额外的参数“ b”在调用中

但问题是,Bar不继承 Foo的任何 init方法,因为它没有满足从其父类继承 init方法的两种特殊情况中的任何一种。

如果这是 Objective-C,我们将毫无问题地继承 init,因为 Objective-C 非常乐意不初始化对象的属性(尽管作为一个开发人员,您不应该对此感到高兴)。在斯威夫特,这根本行不通。不能有无效的状态,继承超类初始值设定项只能导致无效的对象状态。