在Swift子类中添加方便初始化器



作为一个学习练习,我正在尝试实现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")
    }
}
2019年的好消息!我可以报告说,我现在有一个SKShape子类,它有以下三个初始化式:
    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的能力,但你不能拥有一切。然而。

最新更新