Guía del Lenguaje
Desinicialización

Desinicialización

Libera recursos que requieren una limpieza a medida.

Un desinicializador se ejecuta justo antes de la reasignación de una instancia de clase. Los desinicializadores se describen con la palabra clave deinit, de las misma forma que los inicializadores se describen con la palabra clave init. Los desinicializadores sólo están disponibles en las clases.

Cómo funciona la desinicialización

Swift reasigna automáticamente tus instancias cuando dejan de ser necesarias, para liberar recursos. Swift maneja la gestión de memoria de las instancias mediante conteo automático de referencias (ARC), como se describe en Conteo automático de referencias. Normalmente, no hay que hacer limpieza manualmente cuando las instancias son reasignadas. Sin embargo, cuando trabajas con tus propios recursos, es posible que tengas que hacer algo de limpieza adicional. Por ejemplo, si creas una clase personalizada para abrir un archivo y escribir información en él, es posible que tengas que cerrar el archivo antes de la reasignación de la instancia de clase.

Las definiciones de clase sólo pueden tener un desinicializador. El desinicializador no recibe ningún parámetro y se escribe sin paréntesis:

deinit {
    // Realiza la desinicialización
}

Los desinicializadores se llaman automáticamente, justo antes de la reasignación de la instancia. No está permitido llamar directamente a un desinicializador. Las subclases heredan el desinicializador de su superclase, que se ejecuta automáticamente al final de la implementación del desinicializador de la subclase. Los desinicializadores de una superclase siempre se ejecutan, incluso aunque la subclase no tenga un desinicializador propio.

Debido a que una instancia no se reasigna hasta la ejecución de su desinicializador, el desinicializador tiene acceso a todas las propiedades de la instancia en la que es llamado y puede modificar su comportamiento en función de esas propiedades (por ejemplo, consultando el nombre del archivo a cerrar).

Desinicializadores en acción

El siguiente ejemplo ilustra un inicializador en acción. Este ejemplo define dos nuevos tipos para un juego sencillo: Banco y Jugador. La clase Banco gestiona una divisa inventada que nunca puede tener más de 10.000 monedas en circulación. Sólo puede haber un Banco en el juego, definido como una clase con propiedades y métodos para almacenar y gestionar su estado actual:

class Banco {
    static var monedasEnBanco = 10_000
 
    static func distribuir(monedas numeroDeMonedasSolicitadas: Int) -> Int {
        let numeroDeMonedasParaVender = min(numeroDeMonedasSolicitadas, monedasEnBanco)
 
        monedasEnBanco -= numeroDeMonedasParaVender
 
        return numeroDeMonedasParaVender
    }
 
    static func recibir(monedas: Int) {
        monedasEnBanco += monedas
    }
}

Banco lleva la cuenta de la cantidad de monedas que tiene mediante la propiedad monedasEnBanco. También tiene dos métodos (distribuir(monedas:) y recibir(monedas:)) para manejar la distribución y recolección de monedas.

El método distribuir(monedas:) comprueba que hay suficientes monedas en el banco antes de distribuirlas. Si no hay monedas suficientes, Banco devuelve menos monedas que las solicitadas (y devuelve cero si no quedan monedas en el banco). Devuelve un número entero para indicar la cantidad de monedas que han sido proporcionadas.

El método recibir(monedas:) simplemente añade la cantidad de monedas recibidas al conjunto de monedas del banco.

La clase Jugador describe al participante del juego. En cualquier momento, cada jugador tiene una cierta cantidad de monedas almacenadas en su monedero. Esto está representado por la propiedad del jugador llamada monedasEnMonedero:

class Jugador {
    var monedasEnMonedero: Int
 
    init(monedas: Int) {
        monedasEnMonedero = Banco.distribuir(monedas: monedas)
    }
 
    func ganar(monedas: Int) {
        monedasEnMonedero += Banco.distribuir(monedas: monedas)
    }
 
    deinit {
        Banco.recibir(monedas: monedasEnMonedero)
    }
}

Cada instancia de Jugador se inicializa con una asignación inicial de monedas del banco, aunque una instancia de Jugador puede recibir una cantidad menor si no hay suficientes monedas disponibles.

La clase Jugador define el método ganar(monedas:), que recibe una cantidad de monedas del banco y las añade al monedero del jugador. La clase Jugador también tiene un desinicializador, que se ejecuta justo antes de la reasignación de la instancia de Jugador. Aquí el desinicializador simplemente devuelve al banco todas las monedas del jugador:

var jugadorUno: Jugador? = Jugador(monedas: 100)
 
print("Un nuevo jugador se ha unido a la partida con \(jugadorUno!.monedasEnMonedero) monedas")
// Imprime "Un nuevo jugador se ha unido a la partida con 100 monedas"
print("Hay ahora \(Banco.monedasEnBanco) monedas en el banco")
// Imprime "Hay ahora 9900 monedas en el banco"

Se crea una nueva instancia de Jugador con una petición de 100 monedas, si están disponibles. Esta instancia de Jugador se almacena en una variable opcional Jugador llamada jugadorUno. Se usa una variable opcional porque los jugadores pueden abandonar el juego en cualquier momento. La variable opcional permite saber si hay jugadores en el juego.

Debido a que jugadorUno es un opcional, se califica con un signo de exclamación (!) cuando se accede a su propiedad monedasEnMonedero para imprimir la cantidad de monedas por defecto, y cuando se ejecuta su método ganar(monedas:):

jugadorUno!.ganar(monedas: 2_000)
 
print("JugadorUno ha ganado 2000 monedas y ahora tiene \(jugadorUno!.monedasEnMonedero) monedas")
// Imprime "JugadorUno ha ganado 2000 monedas y ahora tiene 2100 monedas"
print("El banco ahora sólo tiene \(Banco.monedasEnBanco) monedas")
// Imprime "El banco ahora sólo tiene 7900 monedas"

Aquí el jugador ha ganado 2.000 monedas. El monedero del jugador ahora contiene 2.100 monedas y el banco sólo tiene 7.900 monedas.

jugadorUno = nil
 
print("JugadorUno ha abandonado el juego")
// Imprime "JugadorUno ha abandonado el juego"
print("El banco ahora tiene \(Banco.monedasEnBanco) monedas")
// Imprime "El banco ahora tiene 10000 monedas"

Ahora el jugador ha abandonado el juego. Esto se indica definiendo la variable opcional jugadorUno como nil, lo que quiere decir que ya no es una instancia de Jugador. Cuando esto ocurre, la referencia a la instancia de jugador de la variable jugadorUno está rota. Ninguna otra propiedad o variable hace referencia a la instancia de Jugador y, por tanto, se reasigna para liberar su espacio de memoria. Justo antes de que esto suceda, su desinicializador se ejecuta automáticamente y sus monedas vuelven al banco.