在Swift子类中添加Convenience Initializers

Ale*_*lav 23 cocoa ios sprite-kit swift

作为一个学习练习,我试图实现一个子类,SKShapeNode它提供了一个新的便利初始化器,它接受一个数字并构造一个ShapeNode,它是一个数字宽度和高度的正方形.

根据Swift Book:

规则1

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

规则2

如果您的子类提供了所有超类指定初始化程序的实现 - 通过按照规则1继承它们,或者通过提供自定义实现作为其定义的一部分 - 那么它会自动继承所有超类便利初始化程序.

但是,以下类不起作用:

class MyShapeNode : SKShapeNode {
    convenience init(squareOfSize value: CGFloat) {
        self.init(rectOfSize: CGSizeMake(value, value))
    }
}
Run Code Online (Sandbox Code Playgroud)

相反,我得到:

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
}
Run Code Online (Sandbox Code Playgroud)

我的理解是MyShapeNode应该继承所有SKShapeNode的便利初始化器,因为我没有实现任何我自己的指定初始化器,并且因为我的便利初始化器是调用init(rectOfSize),另一个方便初始化器,这应该工作.我究竟做错了什么?

mat*_*att 25

这里有两个问题:

  • 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))
    }
}
Run Code Online (Sandbox Code Playgroud)

它看起来是非法的,但事实并非如此.一旦我们调用self.init(),我们就满足了第一个要求,我们现在可以自由地引用self(我们不再在调用self.init之前在委托初始化程序时使用'self')并满足第二个要求需求.

  • "看起来非法".温和地说.这看起来在道德上是错误的,就像"让它摆脱了厌恶一样",这在道德上是错误的.花了两天时间撞到了这堵墙.永远不会发生这种解决方案.有两个独立的初始化器没有明显的连接,只是在空间中悬挂,感觉令人毛骨悚然.我可以确认它有效.你先生是个天才.一个扭曲的,显然,但天才一点也不少. (6认同)
  • @TechZen"扭曲"???? 这种奉承会让你无处可去.但检查是在邮件中. (3认同)

Cez*_*zar 15

我对Initializer继承的理解与你的相同,我认为我们都与本书所述的内容完全一致.我认为这不是解释问题或对所述规则的误解.那就是说,我认为你做错了什么.

我在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))
    }
}
Run Code Online (Sandbox Code Playgroud)

RectShape继承自NSObject并且没有定义任何指定的初始化程序.因此,根据规则1,它继承了所有NSObject的指定初始值设定项.在执行intance的设置之前,我在实现中提供的便利初始化程序正确地委托给指定的初始化程序.

SquareShape继承自RectShape,不提供指定的初始化程序,并且,根据规则1,继承所有SquareShape指定的初始化程序.根据规则2,它还继承了在中定义的便利初始化程序RectShape.最后,SquareShape正确定义的便捷初始化程序将委托给继承的便利初始化程序,后者又委托给继承的指定初始化程序.

所以,鉴于你没有做错任何事情以及我的例子按预期工作,我推断以下假设:

由于SKShapeNode是用Objective-C编写的,因此该语言不强制规定"每个便利初始化器必须从同一个类调用另一个初始化器"的规则.因此,也许便捷初始化SKShapeNode器实际上不会调用指定的初始化器.因此,即使子类MyShapeNode按预期继承了方便初始值设定项,它们也不能正确地委托给继承的指定初始值设定项.

但是,这只是一个假设.我所能确认的是,这些机制在我自己创建的两个类中按预期工作.


Cra*_*lot 5

基于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")
    }
}
Run Code Online (Sandbox Code Playgroud)