Guía del Lenguaje
Tipos Anidados

Tipos Anidados

Define tipos dentro del ámbito de otro tipo.

Las enumeraciones suelen crearse para apoyar la funcionalidad de una clase o estructura específica. De forma similar, puede ser útil definir clases y estructuras utilitarias con el único fin de usarlas dentro del contexto de un tipo más complejo. Para lograr esto, Swift te permite definir tipos anidados, mediante los cuales puedes anidar enumeraciones, clases y estructuras de soporte dentro de la definición del tipo que soportan.

Para anidar un tipo dentro de otro tipo, escribe su definición dentro de las llaves exteriores del tipo que soporta. Los tipos pueden anidarse en tantos niveles como sea necesario.

Tipos anidados en acción

El siguiente ejemplo define una estructura llamada CartaBlackjack, la cual modela una carta tal y como se utiliza en el juego del Blackjack. La estructura CartaBlackjack contiene dos tipos de enumeración anidados llamados Palo y Rango.

En Blackjack, los ases tienen un valor de uno u once. Esta característica está representada por una estructura llamada Valores, la cual se encuentra anidada dentro de la enumeración Rango:

struct CartaBlackjack {
 
    // Enumeración anidada Palo
    enum Palo: Character {
        case picas = "♠", corazones = "♡", diamantes = "♢", treboles = "♣"
    }
 
    // Enumeración anidada Rango
    enum Rango: Int {
        case dos = 2, tres, cuatro, cinco, seis, siete, ocho, nueve, diez
        case jota, reina, rey, _as
 
        // Estructura anidada Valores
        struct Valores {
            let primero: Int, segundo: Int?
        }
 
        var valores: Valores {
            switch self {
            case ._as:
                return Valores(primero: 1, segundo: 11)
            case .jota, .reina, .rey:
                return Valores(primero: 10, segundo: nil)
            default:
                return Valores(primero: self.rawValue, segundo: nil)
            }
        }
    }
 
    // Propiedades y métodos de CartaBlackjack
    let rango: Rango, palo: Palo
 
    var descripcion: String {
        var resultado = "el palo es \(palo.rawValue),"
        resultado += " el valor es \(rango.valores.primero)"
 
        if let segundo = rango.valores.segundo {
            resultado += " u \(segundo)"
        }
 
        return resultado
    }
}

La enumeración Palo describe los cuatro palos habituales de la baraja, junto con un valor Character en bruto para representar su símbolo.

La enumeración Rango describe los trece posibles valores de rango de la baraja, junto con un valor Int en bruto para representar su valor nominal (este valor Int en bruto no se usa para los naipes Jota, Reina, Rey y As).

Como se mencionó anteriormente, la enumeración Rango define una estructura anidada propia, llamada Valores. Esta estructura encapsula el hecho de que la mayoría de las cartas tienen un valor, mientras que la carta As tiene dos valores. La estructura Valores define dos propiedades para representar esto:

  • primero, de tipo Int
  • segundo, de tipo Int?, o "Int opcional"

Rango también define una propiedad calculada, valores, la cual devuelve una instancia de la estructura Valores. Esta propiedad calculada analiza el rango de la carta e inicializa una nueva instancia de Valores con los valores adecuados acorde a su rango. Esta utiliza valores especiales para jota, reina, rey y _as. Para las cartas numéricas, utiliza el valor Int en bruto del rango.

La estructura CartaBlackjack tiene dos propiedades: rango y palo. También define una propiedad calculada llamada descripcion, la cual usa los valores almacenados en rango y palo para componer una descripción del nombre y valor de la carta. La propiedad descripcion utiliza la vinculación opcional para comprobar si hay un segundo valor que mostrar y, de ser así, añade detalles adicionales para la descripción de dicho segundo valor.

Dado que CartaBlackjack es una estructura sin inicializadores a medida, tiene un inicializador de miembros implícito, como se describe enInicializadores de miembros para las estrucuturas. Puedes usar este inicializador para inicializar una nueva constante llamada elAsDePicas:

let elAsDePicas = CartaBlackjack(rango: ._as, palo: .picas)
 
print("elAsDePicas: \(elAsDePicas.descripcion)")
// Imprime "elAsDePicas: el palo es ♠, el valor es 1 u 11"

Aun cuando Rango y Palo están anidadas dentro de CartaBlackjack, su tipo se puede deducir del contexto, por lo que la inicialización de esta instancia se puede permitir referenciar a los casos de la enumeración simplemente por sus nombres de caso (.as y .picas). En el ejemplo anterior, la propiedad descripcion informa, de manera correcta, que el As de Picas tiene un valor de 1 u 11.

Referencias a tipos anidados

Para usar un tipo anidado fuera de su contexto de definición, añade como prefijo a su nombre el nombre del tipo en el que está anidado:

let simboloDeCorazones = CartaBlackjack.Palo.corazones.rawValue
// simboloDeCorazones es "♡"

En el ejemplo anterior, esto permite mantener los nombres de Palo, Rango y Valores deliberadamente cortos, ya que sus nombres están naturalmente cualificados por el contexto en el que se definen.