现有答案中缺少两个绝对关键的特定于Swift的信息,我认为这些信息可以彻底清除这些信息。
- 如果协议将初始化程序指定为必需方法,则必须使用Swift的
required
关键字标记该初始化程序。
- Swift有一组关于
init
方法的特殊继承规则。
该文艺青年最爱的是这样的:
如果实现任何初始化程序,则不再继承任何超类的指定初始化程序。
您将继承的唯一的初始化器(如果有的话)是超类便捷初始化器,它们指向您碰巧要覆盖的指定初始化器。
那么...准备好长版本了吗?
Swift有一组关于init
方法的特殊继承规则。
我知道这是我提出的两点中的第二点,但是我们无法理解第一点,或者为什么在required
理解这一点之前甚至不存在关键字。一旦了解了这一点,另一点就很明显了。
我在此答案本节中涵盖的所有信息均来自Apple的文档,该文档位于此处。
从Apple文档中:
与Objective-C中的子类不同,Swift子类默认情况下不会继承其超类初始化程序。Swift的方法可以防止这样的情况,即超类的简单初始化程序被更专门的子类继承,并用于创建未完全或正确初始化的子类的新实例。
强调我的。
因此,直接从Apple文档中可以看到,Swift子类将不会总是(而且通常不会)继承其超类的init
方法。
那么,它们什么时候从其超类继承?
有两个规则定义子类何时init
从其父类继承方法。从Apple文档中:
规则1
如果您的子类没有定义任何指定的初始化器,它将自动继承其所有超类指定的初始化器。
规则二
如果您的子类提供了其所有超类指定的初始化器的实现(通过按照规则1继承它们,或通过提供自定义实现作为其定义的一部分),那么它将自动继承所有超类便利性的初始化器。
第2条是不是特别相关的这次谈话,因为SKSpriteNode
的init(coder: NSCoder)
不太可能是一个方便的方法。
因此,您的InfoBar
类一直继承required
初始化器,直到您添加为止init(team: Team, size: CGSize)
。
如果您没有提供此init
方法,而是将您InfoBar
添加的属性设为可选或为它们提供了默认值,那么您仍将继承SKSpriteNode
的init(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
没有继承任何的Foo
的init
,因为它没有任何的两种特殊情况下满足了继承方法init
从它的父类的方法。
如果这是Objective-C,我们将init
毫无问题地继承它,因为Objective-C非常满意不初始化对象的属性(尽管作为开发人员,您不应该对此感到满意)。在Swift中,这根本不会做。您不能有无效的状态,继承超类初始化程序只能导致无效的对象状态。
init(collection:MPMediaItemCollection)
。您必须提供真实的媒体项目集合;这就是本课的重点。没有一个类,就无法实例化该类。将要分析集合并初始化许多实例变量。这就是唯一指定的初始值设定项的全部要点!因此,这里init(coder:)
没有有意义的(甚至毫无意义的)MPMediaItemCollection可供提供;只有fatalError
方法是正确的。