Swift类错误:属性未在super.init调用初始化
我有两个class级, Shape
和Square
class Shape { var numberOfSides = 0 var name: String init(name:String) { self.name = name } func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } } class Square: Shape { var sideLength: Double init(sideLength:Double, name:String) { super.init(name:name) // Error here self.sideLength = sideLength numberOfSides = 4 } func area () -> Double { return sideLength * sideLength } }
随着上面的实现,我得到的错误:
property 'self.sideLength' not initialized at super.init call super.init(name:name)
为什么在调用super.init
之前必须设置self.sideLength
?
引用Swift编程语言,它回答你的问题:
“Swift的编译器执行四个有用的安全检查,以确保两阶段初始化完成没有错误:”
安全检查1“指定的初始化程序必须确保所有”由其类引入的属性在将其委派给超类初始化程序之前进行初始化“。
摘录自:苹果公司“Swift编程语言”,iBooks。 https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11
Swift有一个非常明确,特定的操作序列,在初始化器中完成。 让我们从一些基本的例子开始,一直到一般情况。
让我们拿一个对象A.我们将如下定义它。
class A { var x: Int init(x: Int) { self.x = x } }
请注意,A没有超类,所以它不能调用super.init()函数,因为它不存在。
好的,现在我们来创build一个名为B的新类。
class B: A { var y: Int init(x: Int, y: Int) { self.y = y super.init(x: x) } }
这与Objective-C背离,在这之前[super init]
通常会首先被调用。 在Swift中不是这样。 你有责任确保你的实例variables在做任何事情之前都处于一致的状态,包括调用方法(包括你的超类的初始化)。
初始化所有实例variables后,应该调用“super.init()”。
在苹果的“中级快速”video(你可以在苹果开发者video资源页面https://developer.apple.com/videos/wwdc/2014/find它),在大约28:40,明确表示,所有的初始化必须在初始化实例variables之后调用超类。;
在Objective-C中,情况恰恰相反。 在Swift中,由于所有属性在使用之前都需要初始化,所以我们需要先初始化属性。 这是为了防止从超类的“init()”方法调用重写函数,而不是首先初始化属性。
所以“广场”的实施应该是:
class Square: Shape { var sideLength: Double init(sideLength:Double, name:String) { self.sideLength = sideLength numberOfSides = 4 super.init(name:name) // Correct position for "super.init()" } func area () -> Double { return sideLength * sideLength } }
从文档
安全检查1
指定的初始化程序必须确保在其委托给超类初始化程序之前,其类所引入的所有属性都被初始化。
为什么我们需要这样的安全检查?
为了回答这个问题,尽pipe在swift中进行了初始化过程。
两阶段初始化
Swift中的类初始化是一个两阶段的过程。 在第一阶段,每个存储的属性由引入它的类来分配一个初始值。 一旦确定了每个存储属性的初始状态,第二阶段开始,并且每个类都有机会在新实例被认为准备好使用之前进一步定制其存储的属性。
使用两阶段初始化过程可以使初始化安全,同时仍然为类层次结构中的每个类提供完全的灵活性。 两阶段初始化可防止在初始化之前访问属性值 ,并防止其他初始化程序意外地将属性值设置为不同的值。
所以,为了确保两步初始化过程如上所述完成,有四个安全检查,其中之一是,
安全检查1
指定的初始化程序必须确保在其委托给超类初始化程序之前,其类所引入的所有属性都被初始化。
现在,这两个阶段的初始化从来没有谈过命令,但是这个安全检查,在所有属性初始化之后,引入了super.init
命令。
安全检查1可能看起来并不相关,因为在没有进行安全检查的情况下, 两阶段初始化可防止在初始化之前访问属性值 1。
就像这个例子
class Shape { var name: String var sides : Int init(sides:Int, named: String) { self.sides = sides self.name = named } } class Triangle: Shape { var hypotenuse: Int init(hypotenuse:Int) { super.init(sides: 3, named: "Triangle") self.hypotenuse = hypotenuse } }
Triangle.init
已经初始化,每个属性都被使用之前。 所以安全检查1似乎不相关,
但是可能会有另一种情况,有点复杂,
class Shape { var name: String var sides : Int init(sides:Int, named: String) { self.sides = sides self.name = named printShapeDescription() } func printShapeDescription() { print("Shape Name :\(self.name)") print("Sides :\(self.sides)") } } class Triangle: Shape { var hypotenuse: Int init(hypotenuse:Int) { self.hypotenuse = hypotenuse super.init(sides: 3, named: "Triangle") } override func printShapeDescription() { super.printShapeDescription() print("Hypotenuse :\(self.hypotenuse)") } } let triangle = Triangle(hypotenuse: 12)
输出:
Shape Name :Triangle Sides :3 Hypotenuse :12
这里如果我们在设置hypotenuse
之前调用了super.init
, super.init
调用将会调用printShapeDescription()
并且由于它被覆盖了,它将首先回printShapeDescription()
Triangle类实现。 Triangle类的printShapeDescription()
访问尚未初始化的非可选属性的hypotenuse
。 这是不允许的,因为两阶段初始化可以防止在初始化之前访问属性值
所以要确保两阶段的初始化是按照定义完成的,需要调用super.init
的特定顺序,也就是初始化self
类引入的所有属性后,我们需要一个安全检查1
对不起格式丑陋。 只需在申报后提出问题字符,一切都会好的。 一个问题告诉编译器该值是可选的。
class Square: Shape { var sideLength: Double? // <=== like this .. init(sideLength:Double, name:String) { super.init(name:name) // Error here self.sideLength = sideLength numberOfSides = 4 } func area () -> Double { return sideLength * sideLength } }
EDIT1:
有一个更好的方法来跳过这个错误。 根据jmaschad的评论,没有理由在你的情况下使用可选因为optionals不舒服的使用和你总是必须检查如果可选不是零在访问它之前。 所以你所要做的就是在声明后初始化成员:
class Square: Shape { var sideLength: Double=Double() init(sideLength:Double, name:String) { super.init(name:name) self.sideLength = sideLength numberOfSides = 4 } func area () -> Double { return sideLength * sideLength } }
EDIT2:
经过两次这样的回答,我find了更好的办法。 如果你想在你的构造函数中初始化类成员,你必须在构造函数内部和super.init()调用之前给它赋初始值。 喜欢这个:
class Square: Shape { var sideLength: Double init(sideLength:Double, name:String) { self.sideLength = sideLength // <= before super.init call.. super.init(name:name) numberOfSides = 4 } func area () -> Double { return sideLength * sideLength } }
祝你好运,学习Swift。
swift强制你在每个成员variables被使用之前初始化。 由于无法确定超级转弯时会发生什么情况,因此会出错:安全性高于抱歉
爱德华,
你可以像这样修改你的例子中的代码:
var playerShip:PlayerShip! var deltaPoint = CGPointZero init(size: CGSize) { super.init(size: size) playerLayerNode.addChild(playerShip) }
这是使用隐式解包可选。
在文档中,我们可以阅读:
“就可选项而言,如果您在声明隐式解包的可选variables或属性时未提供初始值,则其值将自动默认为零。
Swift不允许你初始化超类,而不是初始化Obj C的属性,所以你必须在调用“super.init”之前初始化所有的属性。
请转到http://blog.scottlogic.com/2014/11/20/swift-initialisation.html 。 它给你的问题一个很好的解释。
将nil添加到声明的末尾。
// Must be nil or swift complains var someProtocol:SomeProtocol? = nil // Init the view override init(frame: CGRect) super.init(frame: frame) ...
这适用于我的情况,但可能不适用于你的情况
你只是在错误的顺序启动。
class Shape2 { var numberOfSides = 0 var name: String init(name:String) { self.name = name } func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." } } class Square2: Shape2 { var sideLength: Double init(sideLength:Double, name:String) { self.sideLength = sideLength super.init(name:name) // It should be behind "self.sideLength = sideLength" numberOfSides = 4 } func area () -> Double { return sideLength * sideLength } }
这是一个惊人的愚蠢的devise。
考虑这样的事情:
. . . var playerShip:PlayerShip var deltaPoint = CGPointZero init(size: CGSize) { super.init(size: size) playerShip = PlayerShip(pos: CGPointMake(self.size.width / 2.0, 100)) playerLayerNode.addChild(playerShip) } . . .
如上所述,这是无效的。 但是:
. . . var playerShip:PlayerShip = PlayerShip(pos: CGPointMake(self.size.width / 2.0, 100)) var deltaPoint = CGPointZero init(size: CGSize) { super.init(size: size) playerLayerNode.addChild(playerShip) } . . .
因为“自我”没有被初始化。
我真心希望这个错误很快就会解决。
(是的,我知道我可以创build一个空的对象,然后设置大小,但这只是愚蠢的)。