首页 文章

在Swift子类中添加Convenience Initializers

提问于
浏览
22

作为一个学习练习,我试图实现一个 SKShapeNode 的子类,它提供了一个新的便利初始化器,它接受一个数字并构造一个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) ,另一个便利初始化器,这应该工作 . 我究竟做错了什么?

3 回答

  • 12

    我对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))
        }
    }
    

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

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

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

    由于 SKShapeNode 是用Objective-C编写的,因此该规则规定"every convenience initializer must call another initializer from the same class"不是由该语言强制执行的 . 因此,也许 SKShapeNode 的便利初始化程序实际上并不调用指定的初始化程序 . 因此,即使子类 MyShapeNode 按预期继承了便利初始值设定项,它们也无法正确委托给继承的指定初始值设定项 .

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

  • 4

    这里有两个问题:

    • 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))
        }
    }
    

    它看起来是非法的,但它不是't. Once we',我们满足了第一个要求,我们现在可以自由地引用 self (我们不再得到"use of 'self' in delegating initializer before self.init is called"错误)并满足第二个要求 .

  • 25

    基于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")
        }
    }
    

相关问题