类没有实现其超类所需的成员
所以我今天更新了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特定信息片断,我认为这些信息已经完全清除了。
- 如果一个协议指定一个初始化器作为必需的方法,那么这个初始化器必须使用Swift的
required
关键字进行标记。 - 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与这个对话不是特别相关,因为SKSpriteNode
的init(coder: NSCoder)
不太可能是一个方便的方法。
所以,你的InfoBar
类inheritance了required
初始化器,直到你添加了init(team: Team, size: CGSize)
。
如果你没有提供这个init
方法,而是使你的InfoBar
的附加属性可选或者提供了默认值,那么你仍然inheritanceSKSpriteNode
的init(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初始化的唯一方法是不提供我们自己的。 所以如果我们试图通过删除Bar
的init(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) }