Swift – Enumeraciones

Get real time updates directly on you device, subscribe now.

En este Tutorial Swift  aprenderemos sobre las Enumeraciones, para qué son útiles, su implementación y como siempre, todo esto lo veremos a través de varios ejemplos de uso.

Las enumeraciones usualmente se definen como un conjunto de datos de un mismo tipo que agrupa valores que se relacionan entre sí. Pero esta definición no es del todo acertada en Swift ya que en este lenguaje las enumeraciones son mucho más flexibles.

[ads1]

Una prueba de esto último sería el hecho de no tener que proporcionar un valor para cada caso de la enumeración ya que el compilador lo infiere al mismo tiempo que sus valores pueden ser presentados como distintos tipos de datos, es decir, que podemos proporcionar un valor para cada elemento de la enumeración sin necesidad de definir un tipo. Al mismo tiempo que cada uno de estos valores pueden ser de cualquier tipo de dato sin que esto sea contemplado como un error.

Algo realmente único es que en Swift las enumeraciones adoptan muchas características tradicionalmente soportadas por las clases, me refiero a las propiedades computadas (computed properties) y a los métodos instancia que nos permiten añadir información extra y funcionalidad relacionado con el valor asociado a cierto elemento de la enumeración, respectivamente.

Las enumeraciones también pueden definir inicializadores para proporcionar un valor inicial para cada caso, se pueden expandir para ampliar su funcionalidad más allá de su implementación original, y pueden apoyarse en los protocolos para proporcionar funcionalidades estándar.

Sintaxis

Declaramos una enumeración con la palabra clave enum y colocamos toda su definición dentro de un par de llaves:

enum SomeEnumeration {

   // La definición va aquí

} // enum

He aquí el primer ejemplo donde definimos los cuatro puntos cardinales:

enum CompassPoint {

   case Norte
   case Sur
   case Este
   case Oeste

} // enum

Los valores definidos en esta enumeración (como Norte, Sur, Este y Oeste) son a los que llamamos los casos de la enumeración. Para esto, tal y como podemos observar, utilizamos la palabra clave case para definir nuevos casos.

Múltiples casos pueden aparecer en una sola línea, separados por comas:

enum Planet {

   case Mercurio, Venus, Tierra, Marte, Júpiter, Saturno, Urano, Neptuno

} // enum

Cada definición de una enumeración define un nuevo tipo. Al igual que otros tipos en Swift, sus nombres (como CompassPoint y Planet) deben comenzar con una letra mayúscula.

Podemos trabajar con los casos de nuestra enumeración de la siguiente forma:

var directionToHead = CompassPoint.Oeste

El tipo de directionToHead se infiere cuando se inicializa con uno de los valores posibles de CompassPoint. Una vez que directionToHead se ha declarado como de tipo CompassPoint, podemos igualuarla a otro de los casos de CompassPoint y en esta ocasión utilizando una sintaxis mucho más corta: el punto.

directionToHead = .Este

El tipo de directionToHead ya es conocido, por lo que podemos omitir el tipo al establecer su valor.

La sentencia switch y las enumeraciones

Podemos también desde una sentencia switch trabajar con enumeraciones:

directionToHead = .South

switch directionToHead {

   case .North:

      print("Un montón de planetas tienen un norte")

   case .South:

      imprimir("Ten cuidado con los pingüinos")

   case .East:

      print("Donde sale el sol")

   case .West:

      print("Donde el cielo es azul")

} // switch

…la salida en pantalla sería:

Ten cuidado con los pingüinos

Este código se pudiera leer de la siguiente forma:

“Considere el valor de directionToHead. En el caso en el que sea igual a .North, imprime “Un montón de planetas tienen un norte”. En el caso en el que sea igual .South, imprimir “Ten cuidado con los pingüinos”.”

…etcétera.

La sentencia Switch debe ser exhaustiva al considerar los casos de una enumeración. Si se omite el caso de .West por ejemplo, este código no compilaría, ya que no se estaría teniendo en cuenta la lista completa de los casos de CompassPoint. Es necesario ser bien cuidadosos con esto, para así asegurar que los casos de la enumeración no se omitan accidentalmente.

[ads2]

Cuando no es conveniente establecer una funcionalidad por cada caso de la enumeración, se puede proporcionar un caso por defecto para cubrir los casos que no se tratan de forma explícita, ejemplo:

let somePlanet = Planet.Earth

switch somePlanet {

   case .Earth:

      print("Mayormente inofensivo")

   default:

      print("No es un lugar seguro para los seres humanos")

} // switch

…la salida en pantalla:

Mayormente inofensivo

Valores Raw implicitamente asignados

Cuando trabajamos con enumeraciones que almacenan enteros o cadenas, usted no tiene que asignar explícitamente un valor para cada caso, cuando esto no se hace, Swift asignará automáticamente los valores por nosotros.

Por ejemplo, cuando se utilizan enteros para valores raw, el valor implícito para cada caso es uno más que el caso anterior. Si el primer caso no tiene un conjunto de valores, su valor es 0.

En el siguiente ejemplo mostramos una enumeración con valores raw enteros para representar el orden de cada planeta del sistema solar:

enum Planet: Int {

   case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune

} // enum

En el ejemplo anterior, Planet.Mercury tiene un valor raw explícito de 1, Planet.Venus tiene un valor implícito de 2, y así sucesivamente.

Lo mismo sucede cuando se utilizan cadenas de valores raw, el valor implícito para cada caso es el texto del nombre de ese caso.

La enumeración del ejemplo siguiente cuenta con valores raw de cadena para representar el nombre de cada dirección:

enum CompassPoint: String {

case Norte, Sur, Este, Oeste

} // enum

En el ejemplo anterior, CompassPoint.South tiene un valor raw implícito de “Sur”, y así sucesivamente. Podemos acceder al valor raw de un caso de la enumeración con su propiedad rawValue:

let earthsOrder = Planet.Earth.rawValue

let sunsetDirection = CompassPoint.West.rawValue

El valor de EarthsOrder es 3 y el de SunsetDirection es “Occidente”

Valores asociados

A veces es útil poder almacenar valores en distintos tipos de datos y tenerlos asociados a cierto caso de nuestra enumeración. Esto nos permitiría almacenar información personalizada adicional junto con el valor del caso. Por ejemplo cuando trabajábamos con CompassPoint veíamos 4 casos que hacían referencia a los puntos cardinales, bueno, pues quizás nos resulte útil, almacenar junto al valor que representa en sí, también las coordenadas de nuestra posición.

[ads3]

Podemos definir enumeraciones y almacenar valores asociados de cualquier tipo, y de ser necesario, los tipos de datos de estos casos pueden ser diferente para cada caso de la enumeración, es decir un caso puede recibir dos enteros, el siguiente una cadena de caracteres, y el tercero cuatro números de coma flotante, etcétera.

En el siguiente ejemplo podemos ver todo lo antes explicado:

//: Playground - noun: a place where people can play

import UIKit

enum HeatLevel {
    
    case Off
    case Low(stove: Int)
    case Medium(stove: Int)
    case High(stove: Int)
    case VeryHigh(stove: Int)
    
    func levelMessage() -> String {
        
        switch self {
            
        case .Off:
            
            return "Off"
            
        case .Low:
            
            return "Low"
            
        case .Medium:
            
            return "Medium"
            
        case .High:
            
            return "High"
            
        case .VeryHigh:
            
            return "Very High"
            
        } // switch
        
    } // levelMessage
    
} // HeatLevel

class InductionRangeCooker {
    
    let stovesNumber: Int
    
    var stovesHeatLevel = [HeatLevel]()
    
    var powerCordConnected: Bool
    
    let ovenAvailable: Bool
    
    init(withNumberOfStovesOf stoves: Int, withInitialHeatLevelOf currentLevel: HeatLevel?, withOven ovenAvailable: Bool, withPowerCordConnected powerCordConnected: Bool) {
        
        self.stovesNumber = stoves
        
        self.ovenAvailable = ovenAvailable
        
        self.powerCordConnected = powerCordConnected
        
        initStovesHeatLevel()
        
        addElement(currentLevel)
        
    } // init
    
    func initStovesHeatLevel() {
        
        for _ in 1...stovesNumber {
            
            stovesHeatLevel.append(HeatLevel.Off)
            
        } // for
        
    } // initStovesHeatLevel
    
    func getStoveNumber(stove: HeatLevel) -> Int {
        
        switch stove {
            
        case HeatLevel.Off:
            
            return 0
            
        case HeatLevel.Low(let currentStove):
            
            return currentStove
            
        case HeatLevel.Medium(let currentStove):
            
            return currentStove
            
        case HeatLevel.High(let currentStove):
            
            return currentStove
            
        case HeatLevel.VeryHigh(let currentStove):
            
            return currentStove
            
        } // switch
        
    } // getStoveNumber
    
    func addElement(stove: HeatLevel?) {
        
        guard let unwrappedStove = stove else {
            
            return
            
        } // guard
        
        stovesHeatLevel[(getStoveNumber(unwrappedStove) - 1)] = unwrappedStove
        
    } // addElement
    
    func showStatusMessage() {
        
        print("\nThis Induction Range Cooker \(ovenAvailable ? "has an oven": "doesn't have an oven"), it's \(powerCordConnected ? "connected to the wall socket" : "not connected to the wall socket"), the number of elements it includes is \(stovesNumber) and the current heat levels are:\n")
        
        for stove in stovesHeatLevel {
            
            print("The stove: \(getStoveNumber(stove)) has a heat level of: \(stove.levelMessage())")
            
        } // for
        
    } // showStatusMessage
    
} // InductionRangeCooker

var myInductionCooker = InductionRangeCooker(withNumberOfStovesOf: 4, withInitialHeatLevelOf: HeatLevel.Low(stove: 2), withOven: true, withPowerCordConnected: true)

var simpleIndcutionCooker = InductionRangeCooker(withNumberOfStovesOf: 2, withInitialHeatLevelOf: HeatLevel.VeryHigh(stove: 1), withOven: false, withPowerCordConnected: true)

myInductionCooker.showStatusMessage()

simpleIndcutionCooker.showStatusMessage()

De la línea 5 a la 41 tenemos la declaración de un enum llamado HeatLevel. Este enum representa la temperatura de cada hornilla de nuestra cocina de inducción (sí, así mismo, este será el objeto con el que trabajaremos, fue lo que se me ocurrió) en varios estados, estos últimos son los casos declarados de la línea 7 a la 11.

En estas línea es donde aplicamos la asociación de valores, como podemos observar a diferencia del primero, todos los casos restantes terminan con el segmento (stove: Int) y en esto consiste la asociación de valores, estamos relacionando a cada uno de estos casos un valor entero al que nombramos como stove.

El objetivo que perseguimos al asociar este valor a cada caso de la enumeración es que cuando se establezca un nivel de temperatura también se especifique la hornilla a la que le aplicaremos este valor.

De la línea 15 a la 17 dentro de la función levelMessage() declaramos una sentencia switch, en la cual tal y como explicamos anteriormente en este artículo, tenemos que incluir todos los casos de la enumeración, de lo contrario el compilador nos mostrará un mensaje de error que nos impedirá compilar el código, la única forma de solventar esto sin especificar todos los casos es declarando default: dentro del bloque switch. Esta función determina el estado de la hornilla y lo devuelve en una cadena de texto.

Luego de las línea 43 a la 130 tenemos la declaración de la clase que representa una cocina de inducción. En esta tenemos varios códigos que se van del tema de este artículo excepto el método getStoveNumber() el cual dado una enumeración de tipo HeatLevel nos devuelve el valor asociado a esta, es decir nos devuelve el entero que representa la hornilla a la que está aplicada el nivel de temperatura que representa ese caso.

Como podemos observar es bastante rudimentaria en esta versión de Swift 2 la manera que tenemos para determinar el valor asociado a un caso de una enumeración, esto no me gusta para nada y personalmente jamás implementaría algo así, no por el hecho de que tengamos que iterar por cada caso ya que en Swift los bloquees switch no funcionan de esa manera (es decir no son Fallthrough) por lo que cuando encuentran un resultado positivo automáticamente terminan su ejecución, no es necesario una sentencia break) es que sencillamente no es la solución más óptima, lo muestro solamente para que puedan ver la manera con la que actualmente contamos para obtener estos valores asociados, demasiado trabajo para algo que podemos lograr de otras formas más sencillas.

Como ya comenté, el resto de código no tiene nada que ver con las enumeración por lo que de comentarlo solamente haría este artículo mucho más extenso añadiendo información no relacionada con el tema que estamos discutiendo

Quizás hayan fragmentos de código que no entiendan, ya que en este ejemplo hay varias técnicas o funcionalidades de Swift de las que no he escrito aun en este sitio, los invito a que si no entienden algo, o sencillamente tiene alguna duda, no duden en dejar un comentario formulando su inquietud. Yo con mucho gusto las contestaré y luego profundizaré sobre el tema en un futuro Tutorial Swift.

La salida del código anterior es la siguiente:

This Induction Range Cooker has an oven, it's connected to the wall socket, the number of elements it includes is 4 and the current heat levels are:

The stove: 0 has a heat level of: Off
The stove: 2 has a heat level of: Low
The stove: 0 has a heat level of: Off
The stove: 0 has a heat level of: Off

This Induction Range Cooker doesn't have an oven, it's connected to the wall socket, the number of elements it includes is 2 and the current heat levels are:

The stove: 1 has a heat level of: Very High
The stove: 0 has a heat level of: Off

Falta aún mucho por aprender en nuestro camino a convertirnos en iOS Developer. Suscríbete a nuestra lista de correo mediante el formulario en el panel derecho y síguenos en nuestras redes sociales. Mantente así al tanto de todas nuestras publicaciones futuras.

Espero que todo cuanto se ha dicho aquí, de una forma u otra le haya servido de aprendizaje, de referencia, que haya valido su preciado tiempo.

Este artículo, al igual que el resto, será revisado con cierta frecuencia en pos de mantener un contenido de calidad y actualizado.

Cualquier sugerencia, ya sea errores a corregir, información o ejemplos a añadir será, más que bienvenida, necesaria!

Get real time updates directly on you device, subscribe now.

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More

RECIBE CONTENIDO SIMILAR EN TU CORREO

RECIBE CONTENIDO SIMILAR EN TU CORREO

Suscríbete a nuestra lista de correo y mantente actualizado con las nuevas publicaciones.

Se ha suscrito correctamente!