Estructuras en Swift

Estructuras Swift struct

Swift provee otro tipo de dato que el programador puede utilizar para crear tipos compuestos, muy parecidos a las clases pero que tienen algunas diferencias. En este capítulo vamos a estar hablando de las estructuras en Swift. Como muchos de los puntos ya fueron explicados en el capítulo de clases, trataremos de ser más concisos esta vez.

Definición de estructura

 

Si quisiéramos definir de una forma rápida qué es una estructura, podríamos decir que se trata de un tipo de dato que permite agrupar varios elementos de distinto tipo bajo un mismo nombre. En este sentido, podríamos tener una estructura compuesta un por un String, un Int y un Bool, por solo dar un ejemplo.

Si bien esto es cierto para la mayoría de los lenguajes de programación, en Swift una estructura es muy parecida a las clases ya que poseen varios puntos en común. Uno de los más importantes es que se pueden incluir métodos o funciones a las estructuras en Swift.

Por lo tanto, una mejor definición sería que las estructuras, al igual que las clases, son bloques flexibles y de propósito general que definen el comportamiento general de una aplicación.

A su vez, existen varias diferencias entre las clases y estructuras. Una de las más importantes es que las estructuras no permiten la herencia entre ellas, como vimos en el capítulo de Clases en Swift.

Estructuras en Swift – Sintaxis

Para poder crear una estructura se utiliza la palabra reservada struct seguido del nombre de la misma. Como se trata de un tipo de dato, se espera, según la convención de nombres, que su nombre comience con una letra mayúscula:

[code lang=”Swift”]
struct UnaEstructura {

//Definición de la estructura

}
[/code]

De manera contraria, todas las funciones que la estructura contenga deberán nombrarse usando una letra en minúscula al inicio. En todos los casos, si los nombres a usar contienen más de una palabra, no se deberán usar ningún separador entre ellas (como guiones o barras) sino que se deberá usar mayúsculas para la primer letra de cada palabra (notación camel case).

Veamos un ejemplo de una estructura:

[code lang=”Swift”]
struct Ubicacion {
var latitud:Double = -34.599722
var longitud:Double = -58.381944
}
[/code]

En este caso estamos creando una estructura llamada Ubicación que contiene dos variables del tipo Double; una para marcar la longitud y otro para la latitud, a los cuales se les asigna un valor correspondiente a la ubicación de Buenos Aires en Argentina.

Estructuras en Swift – proceso de inicialización

 

Como vimos en el capítulo de clases, crear una instancia implica asignar un espacio en memoria para que la clase o estructura tenga entidad propia y así poder usar sus elementos. La forma de hacerlo es crear una variable o constante del tipo de esa clase o estructura y usar de ahí en más la variable o constante en cuestión, haciendo uso de todos los elementos que fueron declarados y programados para esa clase o estructura.

Inicializador por defecto

La forma más simple y básica de crear una instancia de una estructura es mediante el uso del nombre de la misma seguido de paréntesis, lo que corresponde al inicializador por defecto, como vemos a continuación:

[code lang=”Swift”]
let buenosAires = Ubicacion()
[/code]

En el ejemplo, estamos creando una constante llamada buenosAires a la cual se le asigna una estructura del tipo Ubicación.

Para poder acceder a sus atributos, simplemente hacemos referencia a ellos usando un punto seguido de la propiedad que queremos usar:

[code lang=”Swift”]
print("Buenos Aires se sitúa en la longitud \(buenosAires.longitud)")

//Devuelve:
//Buenos Aires se sitúa en la longitud -58.381944
[/code]

Los inicializadores por defecto pueden usarse para aquellas estructuras que no tengan definido un inicializador y que tengan todas sus propiedades asignadas a valores por default. Esto se debe a que el inicializador por defecto crea una instancia con todas las propiedades asignadas a esos valores ya definidos en la estructura.

Inicializador de miembro

En el caso de tener una estructura a la cual no se le haya definido ningún inicializador, se puede hacer uso del inicializador de miembro, el cual permite pasar por parámetro los valores iniciales de las propiedades al momento de crear la instancia.

A diferencia del inicializador por defecto, en este caso podemos usarlo incluso si las propiedades de la estructura no tienen valores default.

En este sentido, usando el ejemplo de arriba, podemos generar una instancia de la siguiente manera:

 

[code lang=”Swift”]
let unaUbicacion = Ubicacion(latitud: -34.454345345, longitud: -53.34234235)
[/code]

Inicializador personalizado

En muchos casos, al momento de crear una instancia necesitamos editar algunos puntos del comportamiento del mismo para realizar cálculos usando los valores pasados por parámetro para las propiedades, en general para completar otras propiedades cuyo valor es calculable.

Para poder resolver esta problemática, tenemos los llamados inicializadores personalizados, que son unas funciones que se ejecutan al momento de crear la instancia de la estructura, en donde podemos customizar el comportamiento a nuestro antojo. Para ello, usamos la palabra reservada init seguida de paréntesis:

[code lang=”Swift”]
struct Ubicacion {
   var latitud:Double = -34.599722
   var longitud:Double = -58.381944
   var hemisferio:String?

   init(latitud:Double, longitud:Double) {
       self.latitud = latitud
       self.longitud = longitud

       if latitud > 0 {
           hemisferio = "Norte"
       } else {
           hemisferio = "Sur"
       }
   }
}
[/code]

Como vemos, estamos creando un inicializador que recibe por parámetro una latitud y longitud de manera de poder sobreescribir los valores por defecto que tienen las propiedades de la estructura (las cuales son referenciadas inequívocamente usando la palabra reservada self). Hasta aquí, estamos emulando el inicializador de miembro visto anteriormente. Sin embargo, la funcionalidad extra que estamos agregando es la de decidir si la ubicación en cuestión corresponde al hemisferio Norte o al Sur.

Cabe destacar, que si creamos un inicializador personalizado, ya no podremos usar los otros vistos anteriormente (el de miembro y el que Swift provee por defecto). En el caso de querer usarlos de todas maneras, tendremos que programarlos.

Un punto muy importante a tener en cuenta es que si alguno de los inicializadores creados por nosotros tiene la misma definición que el inicializador de miembro, no podremos usar ambos al mismo tiempo ya que entrarían en conflicto. Para más información, ver el capítulo de funciones en Swift.

La siguiente estructura corresponde a la misma que venimos trabajando a la cual se le agrega el inicializador por defecto únicamente, ya que el otro no puede ser incluido como tal por lo explicado en el párrafo anterior:

[code lang=”Swift”]
struct Ubicacion {

var latitud:Double = -34.599722
var longitud:Double = -58.381944
var hemisferio:String?

init(latitud:Double, longitud:Double) {
self.latitud = latitud
self.longitud = longitud

if latitud > 0 {
hemisferio = "Norte"
} else {
hemisferio = "Sur"
}
}

//Inicializador por defecto
init() {

}

/*
Este no puede ser incluido ya que tiene la misma definición que el primer inicializador
//Inicializador de miembro
init(latitud:Double, longitud:Double) {
self.latitud = latitud
self.longitud = longitud
}
*/

}
[/code]

Estructura en Swift – tipos por valor

Las estructuras, al igual que las enumeraciones que veremos en un capítulo posterior, son tipos por valor. Esto quiere decir que cada vez que asignamos una estructura a una variable o constante, o cuando esa estructura es pasada por parámetro a una función, el valor es copiado.

Este concepto es muy importante y para entenderlo bien lo vamos a ver con un ejemplo. Supongamos que tenemos una estructura Empleado y que creamos una instancia de ella:

[code lang=”Swift”]
struct Empleado {
var nombre:String
var edad:Int
}

var steve = Empleado(nombre: "Steve", edad: 50)
[/code]

Lo que sucede es que en la memoria se está creando un espacio en donde se guardan los datos de la instancia steve. Si copiamos esa estructura en otra constante o variable:

[code lang=”Swift”]
let otroSteve = steve
[/code]

lo que estamos logrando es la creación de otro espacio en memoria distinto en donde se copian todos los datos de la estructura original. Por lo tanto, si modificamos uno de ellos, el otro no se ve afectado por tratarse de instancias distintas que apuntan a espacios de memoria distintos.

 

[code lang=”Swift”]
steve.nombre = "Bill"

print("\(steve.nombre)") // Bill
print("\(otroSteve.nombre)") // Steve
[/code]

Este comportamiento es completamente distinto al de las clases ya que éstas son tipos por referencia, lo que genera que una copia de una clase en otra provoque que ambas apunten a la misma dirección de memoria y por lo tanto, al cambiar algo en una de ellas, se modifique en la otra.

Estructuras en Swift – métodos

Como ya vimos en el capítulo anterior, los métodos son funciones que podemos agregar a nuestras estructuras y forman parte del comportamiento que la misma posee. En pocas palabras, definen lo que esa estructura es capaz de hacer.

Nota
Todas las reglas aplicadas a los funciones de una estructura son las mismas que vimos en el capitulo de funciones, como las etiquetas de argumento, los tipos de parámetros que existen, el uso de self, etcétera. Por lo tanto, recomendamos releerlo en caso de ser necesario.

 

Métodos de instancia comunes

Son los métodos más simples que vamos a encontrar en cualquier código Swift y se caracterizan por no modificar ningún elemento de la estructura. En general devuelven un resultado o realizan una impresión por consola.

[code lang=”Swift”]
struct Empleado {
var nombre:String
var edad:Int

func saludar() {
print("Hola \(nombre)!")
}
}

steve.saludar()

//Devuelve
//Hola Bill!
[/code]

Métodos de instancia que modifican a la estructura

Como fue explicado párrafos atrás, las estructuras son tipos por valor. Esto provoca que por defecto sus propiedades no puedan ser modificadas dentro de la estructura por sus propios métodos.

Sin embargo, en caso de necesitar modificar estos valores dentro de la estructura mediante alguno de sus métodos de instancia, se debe marcar a la misma con la palabra reservada mutating.

[code lang=”Swift”]
struct Empleado {
var nombre:String
var edad:Int

func saludar() {
print("Hola \(nombre)!")
}

mutating func cambiarEdad(por nuevaEdad:Int) {
edad = nuevaEdad
}
}
[/code]

En el ejemplo anterior, agregamos una función para poder cambiar la edad del empleado. Como la edad es una propiedad, incluso si la misma está declarada como variable, su modificación no es permitida por default y por lo tanto debemos usar la palabra mutating para modificar este comportamiento.

[code lang=”Swift”]
steve.cambiarEdad(por: 55)
[/code]

Modificación de la estructura completa

 

Otra opción que nos permite Swift es la posibilidad de modificar completamente la estructura desde sus métodos de instancia usando la propiedad self. Lo que hace esta técnica es crear una nueva instancia y reemplazar a la ya existente.

[code lang=”Swift”]
struct Empleado {
var nombre:String
var edad:Int

func saludar() {
print("Hola \(nombre)!")
}

mutating func cambiarEdad(por nuevaEdad:Int) {
edad = nuevaEdad
}

mutating func cambiarEmpleado(a nuevoEmpleado:Empleado) {
self = nuevoEmpleado
}
}

steve.cambiarEmpleado(a: Empleado(nombre: "Gabriel", edad: 30))
steve.saludar()

//Devuelve:
//Hola Gabriel!
[/code]

Lo que sucede en ejemplo es que creamos una nueva función que recibe por parámetro a un Empleado (al estilo de las funciones recursivas que reciben un tipo de dato semejante a ellas) y mediante el uso de self modificamos completamente los valores de la estructura steve. Como estamos modificando su valor, debemos forzosamente utilizar la palabra mutating.

Información adicional
Estos métodos se llaman de instancia porque solamente pueden ser usados una vez que se ha asignado el espacio en memoria correspondiente a la estructura. Su contrapartida son los métodos de tipo.

 

Métodos de tipo

Este tipo de métodos, a diferencia de los de instancia, pueden ser invocados sin necesidad de crear una instancia de esa estructura. Por consiguiente, los métodos de tipo son invocados usando el nombre del tipo justamente. Para indicar que se va a usar este tipo de métodos, necesitamos incluir la palabra reservada static antes de func.

Estos métodos no son propiedad exclusiva de las estructuras, sino que también podemos usarlos en clases y enumeraciones.

[code lang=”Swift”]
struct Mensajes {
static let bienvenida = "Bienvenido a la empresa"
}

struct Empleado {
var nombre:String
var edad:Int

func saludar() {
print("Hola \(nombre)!")
}

mutating func cambiarEdad(por nuevaEdad:Int) {
edad = nuevaEdad
}

mutating func cambiarEmpleado(a nuevoEmpleado:Empleado) {
self = nuevoEmpleado
}

func darBienvenida() {
print("\(Mensajes.bienvenida) señor \(nombre)")
}

}

steve.darBienvenida()

//Devuelve
//Bienvenido a la empresa señor Gabriel
[/code]

Como vemos, creamos un struct que contiene un string estático. Luego, en la estructura Empleado agregamos otra función para darle la bienvenida al empleado en cuestión y en ella hacemos uso de la estructura creada previamente pero sin instanciarlo. Directamente hacemos uso de su propiedad bienvenida sin haber hecho antes algo del estilo:

[code lang=”Swift”]
let mensaje = Mensajes()
[/code]

Esto es posible porque, como vimos, los componentes marcados como static pertenecen al tipo (en este caso al tipo Mensajes) y NO a la instancia.

Descarga el archivo playground.
Siguiente capítulo: Enumeraciones.

Si quieres aprender más sobre que es iOS sigue este enlace con una guía definitiva sobre el mejor Sistema Operativo para móviles.
¿Te ha gustado este artículo?
Estructuras en Swift Overall rating: ★★★★★ 5 based on 4 reviews
5 1

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *