Ошибка в classе 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) 

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

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

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

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

Выдержка из: Apple Inc. «Язык быстрого программирования». 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 не имеет суперclassа, поэтому он не может вызвать функцию super.init (), поскольку он не существует.

Итак, теперь давайте подclass A с новым classом B.

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

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

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

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

В Objective-C это было наоборот. В Swift, поскольку все свойства должны быть инициализированы до его использования, нам нужно сначала инициализировать свойства. Это предназначено для предотвращения вызова функции переопределения из метода «init ()» супер classа без первоначальной инициализации свойств.

Таким образом, реализация «Квадрата» должна быть:

 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

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


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

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

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

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

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

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

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

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

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

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

 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:

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

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

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

Эдвард,

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

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

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

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

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

Swift не позволит вам инициализировать суперclass с инициализацией свойств, обратной Obj C. Таким образом, вы должны инициализировать все свойства перед вызовом super.init.

Перейдите на страницу http://blog.scottlogic.com/2014/11/20/swift-initialisation.html . Это дает хорошее объяснение вашей проблеме.

Добавьте ноль в конец объявления.


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

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

Рассмотрим что-то вроде этого:

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

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

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

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

  • У java есть что-то похожее на свойства C #?
  • Причина использования ivars против свойств в объекте c
  • свойство classа маркировки c # как грязное
  • Как пропустить Получить только свойства в servicestack json serializer?
  • Можно ли установить частную собственность через reflection?
  • Разница между свойством и полем в C # 3.0+
  • objective-C Разница между установкой нуля и выпуском
  • C # настройка / получение свойств classа по имени строки
  • Зачем использовать «virtual» для свойств classа в определениях модели Entity Framework?
  • Создает ли private @property переменную экземпляра @private?
  • Свойство '' не найдено на объекте типа 'id'
  • Давайте будем гением компьютера.