Ошибка в классе Swift: свойство не инициализировано при вызове super.init

У меня есть два класса: 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)

Почему мне нужно установить self.sideLength перед вызовом super.init?

Ответ 1

Цитата из Swift Язык программирования, который отвечает на ваш вопрос:

"Компилятор Swifts выполняет четыре полезные проверки безопасности, чтобы убедиться что двухфазная инициализация завершена без ошибок:"

Проверка безопасности 1 "Назначенный инициализатор должен гарантировать, что все" свойства, введенные его классом, инициализируются до этого делегирует до инициализатора суперкласса. "

Отрывок из: Apple Inc." Быстрый язык программирования ". интерактивные книги. https://itunes.apple.com/us/book/swift-programming-language/id881256329?mt=11

Ответ 2

Swift имеет очень четкую, определенную последовательность операций, выполняемую в инициализаторах. Начнем с некоторых основных примеров и проделаем наш путь до общего случая.

Возьмем объект A. Мы определим его следующим образом.

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

Обратите внимание, что A не имеет суперкласса, поэтому он не может вызвать функцию super.init(), поскольку он не существует.

ОК, так что теперь пусть подкласс A с новым классом с именем B.

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

Это отклонение от Objective-C, где [super init] обычно вызывается первым перед чем-либо еще. Не так в Свифт. Вы несете ответственность за то, чтобы ваши переменные экземпляра находились в согласованном состоянии, прежде чем делать что-либо еще, включая методы вызова (который включает инициализатор вашего суперкласса).

Ответ 3

"super.init()" следует вызывать после инициализации всех ваших переменных экземпляра.

В видеоролике Apple Intermediate Swift (вы можете найти его на странице видеоресурсов разработчика Apple https://developer.apple.com/videos/wwdc/2014/) примерно в 28:40, ясно сказано, что все инициализаторы в суперклассе должны быть вызваны ПОСЛЕ того, как вы инициализируете переменные экземпляра.

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

Ответ 4

Из docs

Проверка безопасности 1

Назначенный инициализатор должен гарантировать, что все свойства введенные его классом, инициализируются до того, как он делегирует до инициализатор суперкласса.


Зачем нам нужна проверка безопасности?

Чтобы ответить на этот вопрос, откройте процесс инициализации в быстром режиме.

Двухфазная инициализация

Инициализация классов в Swift является двухфазным процессом. Во-первых фазе, каждому сохраненному свойству присваивается начальное значение классом который представил его. Как только начальное состояние для каждого сохраненного имущества было определено, начинается вторая фаза, и каждому классу дается возможность настроить свои сохраненные свойства еще до новый экземпляр считается готовым к использованию.

Использование двухфазного процесса инициализации делает инициализацию в то же время предоставляя полную гибкость каждому классу в классе иерархия. Двухфазная инициализация предотвращает значения свойств из доступ к ним до их инициализации, и предотвращает свойство значения от заданного другого значения другим инициализатором неожиданно.

Итак, чтобы убедиться, что процесс инициализации двух шагов выполняется, как определено выше, есть четыре проверки безопасности, одна из которых -

Проверка безопасности 1

Назначенный инициализатор должен гарантировать, что все свойства введенные его классом, инициализируются до того, как он делегирует до инициализатор суперкласса.

Теперь двухфазная инициализация никогда не говорит о порядке, но эта проверка безопасности вводит super.init для упорядочения после инициализации всех свойств.

Проверка безопасности 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(), и поскольку это было переопределено, оно сначала отступило бы от реализации класса Triangle printShapeDescription(), printShapeDescription() класса Triangle получает доступ к hypotenuse необязательному свойству, которое еще не было инициализировано. И это не допускается, поскольку двухэтапная инициализация запрещает доступ к значениям свойств до их инициализации.

Итак, убедитесь, что двухфазная инициализация выполнена так, как определено, должен быть определенный порядок вызова super.init, и, после инициализации всех свойств, введенных классом self, нам нужно Проверка безопасности 1

Ответ 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
    }
}

Edit1:

Существует лучший способ пропустить эту ошибку. Согласно комментарию jmaschad, нет оснований для использования опциональных в вашем случае вариантов, которые не удобны в использовании, и вам всегда нужно проверить, не является ли опциональным ни один из них, прежде чем обращаться к нему. Итак, все, что вам нужно сделать, это инициализировать член после объявления:

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:

После того, как два минуса вошли в этот ответ, я нашел еще лучший способ. Если вы хотите, чтобы член класса был инициализирован в вашем конструкторе, вы должны назначить ему начальное значение внутри contructor и перед вызовом 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
    }
}

Удачи в изучении Свифта.

Ответ 6

swift принудительно инициализирует каждый член var до того, как он когда-либо будет использоваться. Так как он не может быть уверен, что произойдет, когда он перевернется, он ошибается: лучше безопасно, чем извините

Ответ 7

Эдвард,

Вы можете изменить код в своем примере следующим образом:

var playerShip:PlayerShip!
var deltaPoint = CGPointZero

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

Это использование неявно развернутого необязательного.

В документации мы можем прочитать:

"Как и с дополнительными, если вы не предоставляете начальное значение, когда вы объявить неявно развернутую необязательную переменную или свойство, ее значение автоматически по умолчанию равно нулю".

Ответ 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)
    ...

Это сработало для моего дела, но может не работать для вас

Ответ 10

Вы просто инициируете неправильный порядок.

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

Ответ 11

Это ошеломляюще глупый дизайн.

Рассмотрим примерно следующее:

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

Потому что "я" не инициализируется.

Я искренне надеюсь, что эта ошибка будет исправлена ​​в ближайшее время.

(Да, я знаю, что могу создать пустой объект, а затем установить размер, но это просто глупо).