Swift类中的错误:在super.init调用时未初始化属性

JuJ*_*oDi 201 compiler-errors properties swift

我有两节课,ShapeSquare

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

通过上面的实现,我得到错误:

property 'self.sideLength' not initialized at super.init call
    super.init(name:name)
Run Code Online (Sandbox Code Playgroud)

为什么我必须self.sideLength在打电话前设置super.init

Rub*_*ben 164

引用Swift编程语言,它回答了你的问题:

"Swift的编译器执行四个有用的安全检查,以确保完成两阶段初始化而没有错误:"

安全检查1"指定的初始化程序必须确保在委托一个超类初始化程序之前初始化其类引入的所有属性."

摘录自:Apple Inc."The Swift Programming Language."iBooks. https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11

  • 现在,与C++,C#或Java相比,这是一个惊人的变化. (44认同)
  • 实际上似乎有一个.比如在C#中,超类构造函数不应该调用任何可覆盖的(虚拟)方法,因为没有人知道它们如何对未完全初始化的子类做出反应.在Swift中没问题,因为当超类构造函数运行时,子类额外状态很好.而且,在Swift中,除最终方法之外的所有方法都是可以覆盖的. (18认同)
  • 我特别觉得它很烦人,因为这意味着,如果我想创建一个创建自己的子视图的UIView子类,我必须初始化那些没有帧的子视图,然后添加帧,因为我不能引用视图直到AFTER调用super.init为止. (16认同)
  • @MDJ当然可以.老实说,我也没有看到它的附加价值. (11认同)
  • @Janos如果你使属性可选,你不必在`init`初始化它. (5认同)

Hit*_*nki 95

Swift具有在初始化器中完成的非常清晰,特定的操作序列.让我们从一些基本的例子开始,一直到一般情况.

我们来看一个对象A.我们将其定义如下.

class A {
    var x: Int
    init(x: Int) {
        self.x = x
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,A没有超类,因此它不能调用super.init()函数,因为它不存在.

好了,现在让我们用一个名为B的新类子类A.

class B: A {
    var y: Int
    init(x: Int, y: Int) {
        self.y = y
        super.init(x: x)
    }
}
Run Code Online (Sandbox Code Playgroud)

这与Objective-C不同,[super init]后者通常首先被调用.斯威夫特不是这样.在执行任何其他操作之前,您有责任确保实例变量处于一致状态,包括调用方法(包括超类'初始化程序).

  • 这非常有帮助,是一个清晰的例子。谢谢你! (3认同)

小智 36

初始化所有实例变量后,应调用"super.init()".

在Apple的"中级Swift"视频中(您可以在Apple Developer视频资源页面https://developer.apple.com/videos/wwdc/2014/中找到它),大约在28:40,它明确表示所有初始化器都在初始化实例变量后必须调用超类.

在Objective-C中,情况正好相反.在Swift中,由于所有属性都需要在使用之前进行初始化,因此我们需要首先初始化属性.这是为了防止从超类的"init()"方法调用覆盖函数,而不首先初始化属性.

所以"Square"的实现应该是:

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


Ban*_*tor 33

来自文档

安全检查1

指定的初始值设定项必须确保在委托一个超类初始化程序之前初始化其类引入的所有属性.


为什么我们需要这样的安全检查?

要回答这个问题,请通过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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

输出:

Shape Name :Triangle
Sides :3
Hypotenuse :12
Run Code Online (Sandbox Code Playgroud)

这里如果我们调用了super.init之前设置的那个hypotenuse,那么super.init调用就会调用printShapeDescription(),因为它已被覆盖,它将首先回退到Triangle类的实现printShapeDescription().该printShapeDescription()三角类的访问hypotenuse仍然没有被初始化非可选属性.并且这是不允许的,因为两阶段初始化会阻止在初始化属性值之前访问它们

因此,确保按照定义完成两阶段初始化,需要有一个特定的调用顺序super.init,也就是说,在初始化了self类引入的所有属性之后,我们需要进行安全检查1


fnc*_*c12 14

抱歉丑陋的格式化.只需在声明后添加一个问题字符,一切都会好的.一个问题告诉编译器该值是可选的.

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

EDIT1:

有一种更好的方法可以跳过此错误.根据jmaschad的评论,没有理由在你的情况下使用可选项因为选项在使用中不舒服而且你必须在访问它之前检查optional是否不是nil.所以你要做的就是在声明后初始化成员:

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

EDIT2:

在得到这个答案后,我找到了更好的方法.如果希望在构造函数中初始化类成员,则必须在构造函数内和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
    }
}
Run Code Online (Sandbox Code Playgroud)

祝学习Swift好运.


Dai*_*jan 9

swift强制您初始化每个成员var,然后才能使用它.因为它不能确定当它是超级转弯时会发生什么,所以它会出错:比对不起更安全


Pav*_*rev 7

爱德华,

您可以像这样修改示例中的代码:

var playerShip:PlayerShip!
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerLayerNode.addChild(playerShip)        
}
Run Code Online (Sandbox Code Playgroud)

这是使用隐式解包的可选项.

在文档中我们可以阅读:

"与可选项一样,如果在声明隐式解包的可选变量或属性时未提供初始值,则其值自动默认为nil."


jis*_*ala 6

Swift不会允许你初始化超类而没有初始化属性,反向Obj C.所以你必须在调用"super.init"之前初始化所有属性.

请访问http://blog.scottlogic.com/2014/11/20/swift-initialisation.html.它为您的问题提供了一个很好的解释.


Mic*_*ael 5

在声明的末尾添加nil。


// Must be nil or swift complains
var someProtocol:SomeProtocol? = nil

// Init the view
override init(frame: CGRect)
    super.init(frame: frame)
    ...
Run Code Online (Sandbox Code Playgroud)

这适用于我的情况,但可能不适用于您的情况