returning - ¿Por qué la palabra clave de conveniencia es incluso necesaria en Swift?
must call a designated initializer of the superclass (6)
Aparte de los puntos que otros usuarios han explicado aquí, es mi comprensión.
Siento firmemente la conexión entre el inicializador de conveniencia y las extensiones. En cuanto a mí, los inicializadores de conveniencia son más útiles cuando quiero modificar (en la mayoría de los casos hacer que sea breve o fácil) la inicialización de una clase existente.
Por ejemplo, alguna clase de terceros que usa tiene
init
con cuatro parámetros, pero en su aplicación los dos últimos tienen el mismo valor.
Para evitar escribir más y limpiar su código, puede definir un
convenience init
con solo dos parámetros y dentro de él llamar a
self.init
with last a los parámetros con valores predeterminados.
Dado que Swift admite la sobrecarga de métodos e inicializadores, puede colocar múltiples
init
uno al lado del otro y usar el que considere conveniente:
class Person {
var name:String
init(name: String) {
self.name = name
}
init() {
self.name = "John"
}
}
Entonces, ¿por qué existiría la palabra clave de
convenience
?
¿Qué hace que lo siguiente sea sustancialmente mejor?
class Person {
var name:String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "John")
}
}
Bueno, lo primero que me viene a la mente es que se usa en herencia de clase para la organización y legibilidad del código.
Continuando con tu clase de
Person
, piensa en un escenario como este
class Person{
var name: String
init(name: String){
self.name = name
}
convenience init(){
self.init(name: "Unknown")
}
}
class Employee: Person{
var salary: Double
init(name:String, salary:Double){
self.salary = salary
super.init(name: name)
}
override convenience init(name: String) {
self.init(name:name, salary: 0)
}
}
let employee1 = Employee() // {{name "Unknown"} salary 0}
let john = Employee(name: "John") // {{name "John"} salary 0}
let jane = Employee(name: "Jane", salary: 700) // {{name "Jane"} salary 700}
Con el inicializador de conveniencia puedo crear un objeto
Employee()
sin valor, de ahí la palabra
convenience
De acuerdo con la
documentación de Swift 2.1
, los inicializadores de
convenience
deben cumplir con algunas reglas específicas:
-
Un inicializador de
convenience
solo puede llamar a inicializadores en la misma clase, no en superclases (solo a través, no arriba) -
Un inicializador
convenience
debe llamar a un inicializador designado en algún lugar de la cadena -
Un inicializador de
convenience
no puede cambiar CUALQUIER propiedad antes de llamar a otro inicializador, mientras que un inicializador designado debe inicializar las propiedades que introduce la clase actual antes de llamar a otro inicializador.
Al usar la palabra clave de
convenience
, el compilador Swift sabe que tiene que verificar estas condiciones; de lo contrario, no podría.
Las respuestas existentes solo cuentan la mitad de la historia de
convenience
.
La otra mitad de la historia, la mitad que no cubre ninguna de las respuestas existentes, responde a la pregunta que Desmond ha publicado en los comentarios:
¿Por qué Swift me obligaría a poner la
convenience
frente a mi inicializador solo porque necesito llamar aself.init
desde él?
Lo toqué ligeramente en
esta respuesta
, en la que cubro
varias
de las reglas de inicialización de Swift en detalles, pero el foco principal estaba en la palabra
required
.
Pero esa respuesta seguía abordando algo que es relevante para esta pregunta y esta respuesta.
Tenemos que entender cómo funciona la herencia del inicializador Swift.
Debido a que Swift no permite variables no inicializadas, no se garantiza que herede todos los inicializadores (o ninguno) de la clase de la que hereda. Si subclasificamos y agregamos cualquier variable de instancia no inicializada a nuestra subclase, habremos dejado de heredar inicializadores. Y hasta que agreguemos nuestros propios inicializadores, el compilador nos gritará.
Para ser claros, una variable de instancia no inicializada es cualquier variable de instancia a la que no se le da un valor predeterminado (teniendo en cuenta que los opcionales y los opcionales implícitamente desempaquetados asumen automáticamente un valor predeterminado de
nil
).
Entonces en este caso:
class Foo {
var a: Int
}
a
es una variable de instancia no inicializada.
Esto no se compilará a menos que le demos
a
valor predeterminado:
class Foo {
var a: Int = 0
}
o inicializar
a
en un método de inicializador:
class Foo {
var a: Int
init(a: Int) {
self.a = a
}
}
Ahora, veamos qué pasa si subclasificamos a
Foo
, ¿de acuerdo?
class Bar: Foo {
var b: Int
init(a: Int, b: Int) {
self.b = b
super.init(a: a)
}
}
¿Derecho?
Agregamos una variable y agregamos un inicializador para establecer un valor en
b
para que se compile.
Dependiendo del idioma del que provenga, puede esperar que
Bar
haya heredado el inicializador de
Foo
,
init(a: Int)
.
Pero no lo hace.
¿Y cómo podría?
¿Cómo sabe
Foo
''s:
init(a: Int)
cómo asignar un valor a la variable
b
que agregó
Bar
?
No lo hace.
Por lo tanto, no podemos inicializar una instancia de
Bar
con un inicializador que no pueda inicializar todos nuestros valores.
¿Qué tiene que ver todo esto con la
convenience
?
Bueno, veamos las reglas sobre la herencia del inicializador :
Regla 1
Si su subclase no define ningún inicializador designado, hereda automáticamente todos sus inicializadores designados de superclase.
Regla 2
Si su subclase proporciona una implementación de todos sus inicializadores designados de superclase, ya sea al heredarlos según la regla 1 o al proporcionar una implementación personalizada como parte de su definición, entonces hereda automáticamente todos los inicializadores de conveniencia de superclase.
Observe la Regla 2, que menciona los inicializadores de conveniencia.
Entonces, lo que hace la palabra clave de
convenience
es indicarnos qué inicializadores
pueden heredar las
subclases que agregan variables de instancia sin valores predeterminados.
Tomemos este ejemplo Clase
Base
:
class Base {
let a: Int
let b: Int
init(a: Int, b: Int) {
self.a = a
self.b = b
}
convenience init() {
self.init(a: 0, b: 0)
}
convenience init(a: Int) {
self.init(a: a, b: 0)
}
convenience init(b: Int) {
self.init(a: 0, b: b)
}
}
Tenga en cuenta que aquí tenemos tres inicializadores de
convenience
.
Eso significa que tenemos tres inicializadores que se pueden heredar.
Y tenemos un inicializador designado (un inicializador designado es simplemente cualquier inicializador que no sea un inicializador de conveniencia).
Podemos crear instancias de la clase base de cuatro maneras diferentes:
Entonces, creemos una subclase.
class NonInheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
}
Estamos heredando de
Base
.
Agregamos nuestra propia variable de instancia y no le dimos un valor predeterminado, por lo que debemos agregar nuestros propios inicializadores.
Agregamos uno,
init(a: Int, b: Int, c: Int)
, pero no coincide con la firma del inicializador designado de la clase
Base
:
init(a: Int, b: Int)
.
Eso significa que no estamos heredando
ningún
inicializador de
Base
:
Entonces, ¿qué pasaría si heredamos de
Base
, pero seguimos adelante e implementamos un inicializador que coincide con el inicializador designado de
Base
?
class Inheritor: Base {
let c: Int
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
convenience override init(a: Int, b: Int) {
self.init(a: a, b: b, c: 0)
}
}
Ahora, además de los dos inicializadores que implementamos directamente en esta clase, debido a que implementamos un inicializador que coincide con el inicializador designado de la clase
Base
, podemos heredar todos los inicializadores de
convenience
la clase
Base
:
El hecho de que el inicializador con la firma correspondiente esté marcado como
convenience
no hace ninguna diferencia aquí.
Solo significa que
Inheritor
solo tiene un inicializador designado.
Entonces, si heredamos de
Inheritor
, solo tendríamos que implementar ese inicializador designado, y luego heredaríamos el inicializador de conveniencia de
Inheritor
, lo que a su vez significa que hemos implementado todos los inicializadores designados de
Base
y podemos heredar su Inicializadores de
convenience
.
Sobre todo claridad. De tu segundo ejemplo,
init(name: String) {
self.name = name
}
es obligatorio o designado Tiene que inicializar todas sus constantes y variables. Los inicializadores de conveniencia son opcionales y, por lo general, pueden usarse para facilitar la inicialización. Por ejemplo, supongamos que su clase Persona tiene una variable de género opcional:
var gender: Gender?
donde el género es una enumeración
enum Gender {
case Male, Female
}
podrías tener inicializadores convenientes como este
convenience init(maleWithName: String) {
self.init(name: name)
gender = .Male
}
convenience init(femaleWithName: String) {
self.init(name: name)
gender = .Female
}
Los inicializadores de conveniencia deben llamar a los inicializadores
designados
o requeridos en ellos.
Si su clase es una subclase, debe llamar a
super.init()
dentro de su inicialización.
Una clase puede tener más de un inicializador designado. Un inicializador de conveniencia es un inicializador secundario que debe llamar a un inicializador designado de la misma clase.