【问题标题】:Error in Swift class: Property not initialized at super.init callSwift 类中的错误:在 super.init 调用时未初始化属性
【发布时间】:2014-07-24 03:19:42
【问题描述】:

我有两个班级,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)

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

【问题讨论】:

    标签: properties compiler-errors swift


    【解决方案1】:

    引自 The Swift Programming Language,它回答了你的问题:

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

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

    摘自:Apple Inc. “Swift 编程语言”。电子书。 https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11

    【讨论】:

    • 现在与 C++、C# 或 Java 相比,这是一个惊人的变化。
    • @MDJ 当然可以。老实说,我也没有看到它的附加价值。
    • 其实好像有一个。在 C# 中说,超类构造函数不应该调用任何可覆盖的(虚拟)方法,因为没有人知道它们会如何对未完全初始化的子类做出反应。在 Swift 中是可以的,因为子类额外状态很好,当超类构造函数正在运行时。此外,在 Swift 中,除了 final 方法之外的所有方法都是可覆盖的。
    • 我特别讨厌它,因为这意味着,如果我想创建一个 UIView 子类来创建自己的子视图,我必须初始化那些没有框架的子视图,然后再添加框架,因为在调用 super.init 之后,我无法引用视图的边界。
    • @Janos 如果您将该属性设为可选,则不必在 init 中初始化它。
    【解决方案2】:

    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],然后再调用其他任何东西。在 Swift 中并非如此。在执行任何其他操作(包括调用方法(包括超类的初始化程序))之前,您有责任确保您的实例变量处于一致状态。

    【讨论】:

    • 这非常有帮助,一个清晰的例子。谢谢!
    • 如果我需要这个值来计算y,例如:init(y: Int) { self.y = y * self.x super.init() }
    • 使用类似 init(y: Int, x: Int = 0) { self.y = y * x;自我.x = x; super.init(x: x) } ,也不能直接参照上面的例子为超类调用空构造函数,因为超类名A没有空构造函数
    【解决方案3】:

    来自docs

    安全检查 1

    指定的初始化器必须确保所有属性 由它的类引入的在它委托给一个之前被初始化 超类初始化器。


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

    要回答这个问题,让我们快速完成初始化过程。

    两阶段初始化

    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 类的printShapeDescription() 访问hypotenuse 一个尚未初始化的非可选属性。这是不允许的,因为两阶段初始化会阻止属性值在初始化之前被访问

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

    【讨论】:

    • 很好的解释,为什么绝对应该添加到最佳答案中。
    • 所以你基本上是在说,因为超类的init 可以调用一个(覆盖的)函数......其中该函数访问子类属性,然后避免如果没有设置值,则必须在设置所有值之后调用super。好的有道理。想知道当时 Objective-C 是如何做到的,为什么你必须先调用super
    • 基本上你要指出的是相似到:放置printShapeDescription() 之前 self.sides = sides; self.name = named;这会产生这个错误:@987654341 @。给出 OP 的错误是为了减少运行时错误的“可能性”。
    • 我特别使用了“可能性”这个词,因为如果printShapeDescription 是一个没有引用self 的函数,即它类似于`print("nothing") 那么会有没有问题。 (即使这样编译器也会抛出错误,因为它不是 super 智能)
    • 那么 objc 并不安全。 Swift 是类型安全的,所以不是可选的对象真的需要是 nonnil!
    【解决方案4】:

    应该在初始化所有实例变量后调用“super.init()”。

    在 Apple 的“Intermediate 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
        }
    }
    

    【讨论】:

    • 永远不会猜到。用 super 初始化应该是第一个声明是我的想法。 !!嗯。 swift 的变化很大。
    • 为什么需要这样做?请提供技术原因
    【解决方案5】:

    抱歉,格式很丑。 只需在声明后加上一个问题字符,一切都会好起来的。 一个问题告诉编译器该值是可选的。

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

    编辑1:

    有一个更好的方法可以跳过这个错误。根据 jmaschad 的评论,没有理由在您的情况下使用 optional ,因为 optional 使用起来不舒服,并且您始终必须在访问它之前检查 optional 是否不为零。所以你所要做的就是在声明后初始化成员:

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

    编辑2:

    在这个答案上有两个缺点后,我找到了更好的方法。如果您希望在构造函数中初始化类成员,则必须在构造函数内部和 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 好运。

    【讨论】:

    • 只需切换super.init(name:name)self.sideLength = sideLength。将sideLength 声明为可选会产生误导,并且会在以后您必须强制解包时引入额外的麻烦。
    • 是的,这是一个选项。谢谢
    • 其实你可以有var sideLength: Double,不需要给它赋初始值
    • 如果我真的有一个可选的常量怎么办。我该怎么办?我必须在构造函数中初始化吗?我不明白你为什么必须这样做,但编译器在 Swift 1.2 中抱怨
    • 完美!所有 3 个解决方案都有效“?”,“String()”,但对我来说,问题是我没有“分配”其中一个属性,当我这样做时它有效!谢谢老哥
    【解决方案6】:

    swift 强制您在每个成员 var 曾经/可能被使用之前对其进行初始化。由于无法确定超级转弯时会发生什么,因此会出错:比抱歉更安全

    【讨论】:

    • 这在 IMO 中没有意义,因为父类不应该看到其子类中声明的属性!
    • 它没有,但你可以覆盖一些东西并开始使用 self 'before super made it'
    • 你能看到here 和紧随其后的cmets 吗?我想我说的正是你所说的,即我们都说编译器想要安全而不是抱歉,我唯一的问题是,那么objective-c 是如何解决这个问题的?还是没有?如果没有,那为什么还要在第一行写super.init
    【解决方案7】:

    爱德华,

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

    var playerShip:PlayerShip!
    var deltaPoint = CGPointZero
    
    init(size: CGSize)
    {
        super.init(size: size)
        playerLayerNode.addChild(playerShip)        
    }
    

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

    在文档中我们可以阅读:

    " 与可选项一样,如果您在使用时不提供初始值 声明一个隐式展开的可选变量或属性,它是 value 自动默认为 nil。"

    【讨论】:

    • 我认为这是最干净的选择。在我第一次尝试 Swift 时,我有一个 AVCaptureDevice 类型的成员变量,它不能直接实例化,因此需要 init() 代码。但是,ViewController 需要多个初始化器,并且您不能从 init() 调用通用初始化方法,因此这个答案似乎是避免在每个初始化器中复制/粘贴重复代码的唯一选择。
    【解决方案8】:

    Swift 不允许您在不初始化属性的情况下初始化超类,这与 Obj C 相反。因此您必须在调用“super.init”之前初始化所有属性。

    请转至http://blog.scottlogic.com/2014/11/20/swift-initialisation.html。 它很好地解释了您的问题。

    【讨论】:

      【解决方案9】:

      在声明的末尾添加 nil。


      // Must be nil or swift complains
      var someProtocol:SomeProtocol? = nil
      
      // Init the view
      override init(frame: CGRect)
          super.init(frame: frame)
          ...
      

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

      【讨论】:

      • 好一个,同时使用带有协议的 UIViewController 的 UIView
      【解决方案10】:

      应该是这样的:

      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

      【讨论】:

      • 这对现有答案有何改进?
      • 也可以为sideLength分配一个初始值,这是一个很好的做法,例如:var sideLength: Double = 0.0
      【解决方案11】:

      您只是按错误的顺序启动。

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

      【讨论】:

      • OP 询问原因,而不是如何。
      猜你喜欢
      • 2020-07-06
      • 2014-10-09
      • 2021-12-22
      • 1970-01-01
      相关资源
      最近更新 更多