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

时间:2014-06-03 17:11:19

标签: properties compiler-errors 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
    }
}

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

property 'self.sideLength' not initialized at super.init call
    super.init(name:name)

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

13 个答案:

答案 0 :(得分:155)

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

  

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

     

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

     

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

答案 1 :(得分:83)

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

让我们拿一个对象A.我们将其定义如下。

class A {
    var x: Int
    init(x: Int) {
        self.x = x
    }
}

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

好的,现在让我们用一个名为B的新类继承A。

class B: A {
    var y: Int
    init(x: Int, y: Int) {
        self.y = y
        super.init(x: x)
    }
}

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

答案 2 :(得分:33)

初始化所有实例变量后,应调用“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
    }
}

答案 3 :(得分:30)

来自docs

  

安全检查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
    }
}

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

如果我们在设置super.init之前调用了hypotenuse,那么super.init调用就会调用printShapeDescription(),因为已经覆盖了printShapeDescription(),它将首先回退到printShapeDescription()的Triangle类实现。 Triangle类的hypotenuse访问super.init一个尚未初始化的非可选属性。这是不允许的,因为两阶段初始化会阻止在初始化属性值之前访问它们

因此,确保按照定义完成两阶段初始化,需要有一个特定的调用self的顺序,也就是说,在初始化where(condition, [x, y]) Return elements, either from `x` or `y`, depending on `condition`. If only `condition` is given, return ``condition.nonzero()``. Parameters ---------- condition : array_like, bool When True, yield `x`, otherwise yield `y`. x, y : array_like, optional Values from which to choose. `x` and `y` need to have the same shape as `condition`. 类引入的所有属性之后,因此我们需要安全检查1

答案 4 :(得分: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
    }
}

<强> 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
    }
}

<强> 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
    }
}

学习Swift祝你好运。

答案 5 :(得分:9)

swift强制您在每个成员var被使用之前初始化它们。由于它不能确定当它是超级转弯时会发生什么,它会出错:比抱歉更安全

答案 6 :(得分:7)

爱德华,

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

var playerShip:PlayerShip!
var deltaPoint = CGPointZero

init(size: CGSize)
{
    super.init(size: size)
    playerLayerNode.addChild(playerShip)        
}

这是使用隐式展开的可选项。

在文档中我们可以阅读:

  

&#34;与选项一样,如果您没有提供初始值   声明一个隐式解包的可选变量或属性,它是   值自动默认为nil。&#34;

答案 7 :(得分:6)

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

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

答案 8 :(得分:5)

将nil添加到声明的末尾。

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

// Init the view
override init(frame: CGRect)
    super.init(frame: frame)
    ...

这适用于我的案例,但可能不适合你的

答案 9 :(得分:1)

你刚刚以错误的顺序开始。

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

答案 10 :(得分:0)

我可能会收到一些反对意见,但是老实说,这样生活会更轻松:

class CSListServerData<ListItem: CSJsonData>: CSServerData {
    var key: String!
    var type: ListItem.Type!
    var property: CSJsonDataList<ListItem>!

    func construct(_ key: String, _ type: ListItem.Type) -> Self {
        self.key = key
        self.type = type
        property = CSJsonDataList(self, type, key)
        return self
    }

    func construct(_ type: ListItem.Type) { construct("list", type) }

    var list: [ListItem] { property.list }
}

答案 11 :(得分:0)

应该是这样的:

init(sideLength:Double, name:String) {
    self.sideLength = sideLength
    super.init(name:name)
    numberOfSides = 4
}

看这个链接: https://swiftgg.gitbook.io/swift/swift-jiao-cheng/14_initialization#two-phase-initialization

答案 12 :(得分:-4)

这是一个令人难以置信的愚蠢设计。

考虑这样的事情:

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

因为'self'尚未初始化。

我真诚地希望很快能解决这个问题。

(是的,我知道我可以创建一个空对象然后设置大小,但这只是愚蠢的。)