the returning protocol paths must isn from extension designated called before all swift initialization

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:

  1. Un inicializador de convenience solo puede llamar a inicializadores en la misma clase, no en superclases (solo a través, no arriba)

  2. Un inicializador convenience debe llamar a un inicializador designado en algún lugar de la cadena

  3. 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 a self.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.