作为一个学习练习,我正在尝试实现SKShapeNode
的一个子类,它提供了一个新的方便初始化器,该初始化器接受一个数字并构造一个ShapeNode,该ShapeNode是数字宽度和高度的平方。
根据Swift Book:
规则1
如果你的子类没有定义任何指定初始化式,它会自动继承它的所有父类的指定初始化式。
规则2
如果你的子类提供了所有父类指定初始化器的实现——要么按照规则1继承它们,要么通过提供自定义实现作为其定义的一部分——那么它将自动继承所有父类的方便初始化器。
但是,下面的类不起作用:
class MyShapeNode : SKShapeNode {
convenience init(squareOfSize value: CGFloat) {
self.init(rectOfSize: CGSizeMake(value, value))
}
}
得到:
Playground execution failed: error: <REPL>:34:9: error: use of 'self' in delegating initializer before self.init is called
self.init(rectOfSize: CGSizeMake(value, value))
^
<REPL>:34:14: error: use of 'self' in delegating initializer before self.init is called
self.init(rectOfSize: CGSizeMake(value, value))
^
<REPL>:35:5: error: self.init isn't called on all paths in delegating initializer
}
我的理解是MyShapeNode
应该继承所有SKShapeNode
的方便初始化器,因为我没有实现任何我自己指定的初始化器,而且因为我的方便初始化器调用了init(rectOfSize)
,另一个方便初始化器,这应该工作。我做错了什么?
这里有两个问题:
-
SKShapeNode只有一个指定初始化器:
init()
。这意味着如果不调用init()
,我们就不能退出初始化式。 -
SKShapeNode有一个属性
path
声明为CGPath!
。这意味着我们不想在不初始化path
的情况下退出初始化器。
这两件事的结合是问题的根源。简而言之,SKShapeNode写错了。它有一个必须初始化的属性path
;因此,它应该有一个指定的初始化器来设置path
。但它没有(它所有的path
设置初始化式都是方便的初始化式)。这就是问题所在。换句话说,问题的根源在于,不管方便与否,shapeNodeWith...
方法根本就不是真正的初始化器。
class MyShapeNode : SKShapeNode {
convenience init(squareOfSize value: CGFloat) {
self.init()
self.init(rectOfSize: CGSizeMake(value, value))
}
}
这看起来是违法的,但它不是。一旦我们调用了self.init()
,我们就满足了第一个要求,现在我们可以自由地引用self
(我们不再得到"在self之前委托初始化器时使用'self'")。Init被称为"错误",并且满足第二个要求。
我对初始化继承的理解与您的相同,我认为我们都与书中所述的很好地一致。我不认为这是一个解释问题,或者是对既定规则的误解。也就是说,我不认为你做错了什么。
我在Playground中测试了以下代码,结果与预期的一样:
class RectShape: NSObject {
var size = CGSize(width: 0, height: 0)
convenience init(rectOfSize size: CGSize) {
self.init()
self.size = size
}
}
class SquareShape: RectShape {
convenience init(squareOfSize size: CGFloat) {
self.init(rectOfSize: CGSize(width: size, height: size))
}
}
RectShape
继承自NSObject
,没有定义任何指定初始化式。因此,按照规则1,它继承了NSObject
的所有指定初始化式。在为实例进行设置之前,我在实现中提供的方便初始化器正确地委托给指定的初始化器。
SquareShape
继承自RectShape
,不提供指定初始化式,并且,按照规则1,继承了SquareShape
的所有指定初始化式。按照规则2,它还继承了RectShape
中定义的方便初始化式。最后,SquareShape
中定义的方便初始化器正确地委托给继承的方便初始化器,而继承的方便初始化器又委托给继承的指定初始化器。
所以,鉴于你没有做错任何事,而且我的例子如预期的那样工作,我推断出以下假设:
由于SKShapeNode
是用Objective-C编写的,所以"每个方便初始化器必须从同一个类调用另一个初始化器"的规则并没有被语言强制执行。也许SKShapeNode
的方便初始化器实际上并没有调用指定初始化器。因此,即使子类MyShapeNode
按预期继承了方便初始化器,它们也不能正确地委托给继承的指定初始化器。
但是,再一次,这只是一个假设。我所能确认的是,这个机制在我自己创造的两个职业上都是按照预期运行的。
基于Matt的回答,我们必须包含一个额外的函数,否则编译器会抱怨调用没有参数的初始化式。
下面是SKShapeNode子类的工作:
class CircleNode : SKShapeNode {
override init() {
super.init()
}
convenience init(width: CGFloat, point: CGPoint) {
self.init()
self.init(circleOfRadius: width/2)
// Do stuff
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
override init() {
super.init()
}
convenience init(width: CGFloat, point: CGPoint) {
self.init(circleOfRadius: width/2)
self.fillColor = .green
self.position = point
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
,它的行为完全符合预期:当您调用方便初始化器时,您将在所需位置获得绿色点。(另一方面,@matt和@Crashalot描述的对init()的双重调用现在会导致错误)。
我更喜欢在。sks场景编辑器中修改SKShapeNodes的能力,但你不能拥有一切。然而。