¿Cuál es la diferencia entre conveniencia init vs init en ejemplos rápidos y explícitos?

Respuestas:

105

Estándar init:

Los inicializadores designados son los inicializadores principales de una clase. Un inicializador designado inicializa completamente todas las propiedades introducidas por esa clase y llama a un inicializador de superclase apropiado para continuar el proceso de inicialización en la cadena de superclase.

convenience init:

Los inicializadores de conveniencia son secundarios y admiten inicializadores para una clase. Puede definir un inicializador de conveniencia para llamar a un inicializador designado de la misma clase que el inicializador de conveniencia con algunos de los parámetros del inicializador designado establecidos en valores predeterminados. También puede definir un inicializador de conveniencia para crear una instancia de esa clase para un caso de uso específico o un tipo de valor de entrada.

según la documentación de Swift

En pocas palabras, esto significa que puede usar un inicializador conveniente para hacer que llamar a un inicializador designado sea más rápido y más "conveniente". Por lo tanto, los inicializadores de conveniencia requieren el uso de en self.initlugar del super.initque podría ver en una anulación de un inicializador designado.

ejemplo de pseudocódigo:

init(param1, param2, param3, ... , paramN) {
     // code
}

// can call this initializer and only enter one parameter,
// set the rest as defaults
convenience init(myParamN) {
     self.init(defaultParam1, defaultParam2, defaultParam3, ... , myParamN)
}

Los uso mucho al crear vistas personalizadas y que tienen inicializadores largos con principalmente valores predeterminados. Los documentos hacen un mejor trabajo explicando que yo, ¡échales un vistazo!

treyhakanson
fuente
73

Los inicializadores de conveniencia se usan cuando tienes una clase con muchas propiedades que hacen que sea un poco "doloroso" inicializar siempre el ingenio con todas esas variables, así que lo que haces con el inicializador de conveniencia es que simplemente pasas algunas de las variables para inicializar el objeto y asigne el resto con un valor predeterminado. Hay un video muy bueno en el sitio web de Ray Wenderlich, no estoy seguro de que sea gratis o no porque tengo una cuenta paga. Aquí hay un ejemplo donde puede ver que en lugar de inicializar mi objeto con todas esas variables, solo le estoy dando un título.

struct Scene {
  var minutes = 0
}

class Movie {
  var title: String
  var author: String
  var date: Int
  var scenes: [Scene]

  init(title: String, author: String, date: Int) {
    self.title = title
    self.author = author
    self.date = date
    scenes = [Scene]()
  }

  convenience init(title:String) {
    self.init(title:title, author: "Unknown", date:2016)
  }

  func addPage(page: Scene) {
    scenes.append(page)
  }
}


var myMovie = Movie(title: "my title") // Using convenicence initializer
var otherMovie = Movie(title: "My Title", author: "My Author", date: 12) // Using a long normal initializer
Mago Nicolás Palacios
fuente
4
Buen ejemplo. Esto dejó claro el concepto. Gracias.
Arjun Kalidas
5
También podemos tener init () con valores predeterminados para autor y parámetro de fecha como `init (título: Cadena, autor: Cadena =" Desconocido ", fecha: Int = 2016) {self.title = título self.author = autor self. fecha = fecha escenas = [Escena] ()} `entonces, ¿por qué necesitamos un inicializador de conveniencia?
shripad20
@ shripad20 la misma pregunta aquí. ¿Descubrió por qué tenemos que usar la conveniencia aunque podemos hacer otro inicializador y enviar el parámetro predeterminado desde él? Por favor, avíseme si encuentra la respuesta
user100
la conveniencia es adicional a self.init "principal" mientras que self.init con los parámetros predeterminados reemplaza al self.init "principal". hackingwithswift.com/example-code/language/…
user1105951
@ shripad20 mira mi ejemplo a continuación para una aclaración donde los inicializadores de conveniencia superan los valores predeterminados. Espero eso ayude.
Chris Graf
14

Aquí hay un ejemplo simple, tomado del portal de desarrolladores de Apple .

Básicamente, el inicializador designado es el init(name: String), asegura que todas las propiedades almacenadas se inicialicen.

El init()inicializador de conveniencia, que no acepta argumentos, establece automáticamente el valor de la namepropiedad almacenada en [Unnamed]mediante el inicializador designado.

class Food {
    let name: String

    // MARK: - designated initializer
    init(name: String) {
        self.name = name
    }

    // MARK: - convenience initializer
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

// MARK: - Examples
let food = Food(name: "Cheese") // name will be "Cheese"
let food = Food()               // name will be "[Unnamed]"

Es útil, cuando se trata de clases grandes, con al menos algunas propiedades almacenadas. Recomendaría leer un poco más sobre opcionales y herencia en el portal de desarrolladores de Apple.

sucio
fuente
6

Donde los inicializadores de conveniencia superan la configuración de los valores predeterminados de los parámetros

Para mí, convenience initializersson útiles si hay más que hacer que simplemente establecer un valor predeterminado para una propiedad de clase.

Implementación de clase con init () designado

De lo contrario, simplemente establecería el valor predeterminado en la initdefinición, por ejemplo:

class Animal {

    var race: String // enum might be better but I am using string for simplicity
    var name: String
    var legCount: Int

    init(race: String = "Dog", name: String, legCount: Int = 4) {
        self.race = race
        self.name = name
        self.legCount = legCount // will be 4 by default
    }
}

Extensión de clase con comodidad init ()

Sin embargo, puede haber más cosas que hacer que simplemente establecer un valor predeterminado, y ahí es donde resulta convenience initializersútil:

extension Animal {
    convenience init(race: String, name: String) {
        var legs: Int

        if race == "Dog" {
            legs = 4
        } else if race == "Spider" {
            legs = 8
        } else {
            fatalError("Race \(race) needs to be implemented!!")
        }

        // will initialize legCount automatically with correct number of legs if race is implemented
        self.init(race: race, name: name, legCount: legs)
    }
}

Ejemplos de uso

// default init with all default values used
let myFirstDog = Animal(name: "Bello")

// convenience init for Spider as race
let mySpider = Animal(race: "Spider", name: "Itzy")

// default init with all parameters set by user
let myOctopus = Animal(race: "Octopus", name: "Octocat", legCount: 16)

// convenience init with Fatal error: Race AlienSpecies needs to be implemented!!
let myFault = Animal(race: "AlienSpecies", name: "HelloEarth")
Chris Graf
fuente
1
bien y simple explicado aquí
vivi
4

Se puede definir un inicializador de conveniencia en una extensión de clase . Pero uno estándar, no puede.

Serg
fuente
1
Aunque no es una respuesta completa a la pregunta, esto es algo que aún no había considerado, ¡gracias!
Marc-André Weibezahn
3

la convenience inithace que sea opcional para inicializar una clase con valores.

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Badr
fuente
3

Tiene sentido si su caso de uso es llamar a un inicializador en otro inicializador en la misma clase .

Intenta hacer esto en el patio de recreo

class Player {
    let name: String
    let level: Int

    init(name: String, level: Int) {
        self.name = name
        self.level = level
    }
    
    init(name: String) {
        self.init(name: name, level: 0) //<- Call the initializer above?

        //Sorry you can't do that. How about adding a convenience keyword?
    }
}

Player(name:"LoseALot")

Con palabra clave de conveniencia

class Player {
    let name: String
    let level: Int

    init(name: String, level: Int) {
        self.name = name
        self.level = level
    }
    
    //Add the convenience keyword
    convenience init(name: String) {
        self.init(name: name, level: 0) //Yes! I am now allowed to call my fellow initializer!
    }
}
Jeromegamo
fuente
2

Nota: lea todo el texto

Los inicializadores designados son los inicializadores principales de una clase. Un inicializador designado inicializa completamente todas las propiedades introducidas por esa clase y llama a un inicializador de superclase apropiado para continuar el proceso de inicialización hasta la cadena de superclase.

Los inicializadores de conveniencia son secundarios y admiten inicializadores para una clase. Puede definir un inicializador de conveniencia para llamar a un inicializador designado de la misma clase que el inicializador de conveniencia con algunos de los parámetros del inicializador designado configurados como predeterminados.

Los inicializadores designados para clases se escriben de la misma manera que los inicializadores simples para tipos de valor:

init(parameters) {
statements
}

Los inicializadores de conveniencia están escritos en el mismo estilo, pero con el modificador de conveniencia colocado antes de la palabra clave init, separados por un espacio:

convenience init(parameters) {
statements
}

Un ejemplo práctico es el siguiente:

class Food {
var name: String
init(name: String) {
    self.name = name
}
convenience init() {
    self.init(name: "[Unnamed]")
}
}
let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon”

El inicializador init (name: String) de la clase Food se proporciona como un inicializador designado porque garantiza que todas las propiedades almacenadas de una nueva instancia de Food se inicialicen por completo. La clase Food no tiene una superclase, por lo que el inicializador init (nombre: String) no necesita llamar a super.init () para completar su inicialización.

“La clase Food también proporciona un inicializador de conveniencia, init (), sin argumentos. El inicializador init () proporciona un nombre de marcador de posición predeterminado para un nuevo alimento al delegar en el init de la clase de Alimentos (nombre: Cadena) con un valor de nombre de [Sin nombre]: "

let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]”

La segunda clase en la jerarquía es una subclase de alimentos llamada RecipeIngredient. La clase RecipeIngredient modela un ingrediente en una receta de cocina. Introduce una propiedad Int llamada cantidad (además de la propiedad de nombre que hereda de Food) y define dos inicializadores para crear instancias de RecipeIngredient:

class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
    self.quantity = quantity
    super.init(name: name)
}
override convenience init(name: String) {
    self.init(name: name, quantity: 1)
}
}

La clase RecipeIngredient tiene un único inicializador designado, init (nombre: String, cantidad: Int), que se puede usar para completar todas las propiedades de una nueva instancia de RecipeIngredient. Este inicializador comienza asignando el argumento de cantidad pasado a la propiedad de cantidad, que es la única propiedad nueva introducida por RecipeIngredient. Después de hacerlo, el inicializador delega hasta el inicializador init (nombre: String) de la clase Food.

página: 536 Extracto de: Apple Inc. "El lenguaje de programación Swift (Swift 4)". iBooks. https://itunes.apple.com/pk/book/the-swift-programming-language-swift-4-0-3/id881256329?mt=11

Abuzar Manzoor
fuente
2

Por tanto, resulta útil cuando no es necesario especificar todas y cada una de las propiedades de una clase. Entonces, por ejemplo, si quiero crear todas las aventuras con un valor de HP inicial de 100, usaría el siguiente inicio de conveniencia y solo agregaría un nombre. Esto va a reducir mucho el código.

class Adventure { 

// Instance Properties

    var name: String
    var hp: Int
    let maxHealth: Int = 100

    // Optionals

    var specialMove: String?

    init(name: String, hp: Int) {

        self.name = name
        self.hp = hp
    }

    convenience init(name: String){
        self.init(name: name, hp: 100)
    }
}
Nirav Jain
fuente
1

Todas las respuestas suenan bien pero, entendamos con un ejemplo simple

class X{                                     
   var temp1
   init(a: Int){
        self.temp1 = a
       }

Ahora, sabemos que una clase puede heredar otra clase, así que

class Z: X{                                     
   var temp2
   init(a: Int, b: Int){
        self.temp2 = b
        super.init(a: a)
       }

Ahora, en este caso, mientras crea una instancia para la clase Z, deberá proporcionar los valores 'a' y 'b'.

let z = Z(a: 1, b: 2)

Pero, ¿qué pasa si solo desea pasar el valor de b y desea que el resto tome el valor predeterminado para otros? Entonces, en ese caso, debe inicializar otros valores con un valor predeterminado. Pero espera, ¿cómo ?, para eso U necesitas configurarlo bien antes solo en la clase.

//This is inside the class Z, so consider it inside class Z's declaration
convenience init(b: Int){
    self.init(a: 0, b: b)
}
convenience init(){
    self.init(a: 0, b: 0)
}

Y ahora, puede crear instancias de clase Z proporcionando algunos, todos o ninguno valores para las variables.

let z1 = Z(b: 2)
let z2 = Z()
shubham mishra
fuente