Video tutorial:
¿Utilizar un Struct o una Class? ¿Una Class o un Struct? He ahí la cuestión.
Hoy voy a explicarte cuáles son las diferencia entre una clase y una estructura en Swift y así sepas cuál debes utilizar en cada momento.
Además, esta es una pregunta muy típica en entrevistas iOS para perfiles junior o mid, así que hoy matamos dos pájaros de un tiro.
¿Qué tienen en común?
Antes de hablar de las diferencias entre struct
y class
, hablemos primero de lo que tienen en común:
- Ambos mecanismos se utilizan para crear nuevos tipos en Swift. Tipos que no vienen «de serie» en el lenguaje.
- Los dos pueden tener propiedades y éstas pueden ser de cualquier otro tipo, incluídos aquellos creados mediante structs, classes, protocols, enums, etc…
- Los dos pueden contener métodos.
Es decir, que a priori, ambos sirven para lo mismo y no hay demasiada diferencia entre clase y estructura.
¿Es eso correcto?
Sí y no.
¿Qué los diferencia?
Aunque los dos sirven para lo mismo, internamente sí tienen algunas diferencias importantes, que hacen que la pregunta «struct vs class» tenga sentido:
Value type VS Reference type
Las structs son value types (tipos por valor), mientras que las clases son reference types (tipos por referencia). Y ahora, claro, te estarás preguntando qué carallo es eso de tipo por referencia y tipo por valor.
Los tipos por valor (value types) se copian cuando se asigna a una nueva variable o se pasa como parámetro en una función, mientras que los tipos por referencia (reference types) se comparten.
Como probablemente no sepas a qué me refiero, antes de entrar en el detalle técnico de cada uno voy a utilizar una analogía para que se entienda mejor.
Cuando estaba en la Universidad y tenía que hacer un trabajo grupal, creaba un fichero de Microsoft Word (
.docx
). Cuando terminaba mi parte, se la enviaba a mis compañeros, y ellos modificaban la copia que yo les había enviado añadiendo su parte del trabajo. Pero el documento original, es decir, el mío (el que está en mi ordenador), no cambiaba cuando ellos añadían su parte del trabajo. El que sí se modificaba era su copia. En el mundo del software, un documento.docx
sería un value type.Ahora trabajar en grupo es mucho más sencillo gracias a Google Docs. Con los documentos de Google, si yo envío el link (la referencia) del documento a mis compañeros y ellos lo modifican, en realidad están modificando el mismo documento. El original. El único. En este caso no co-existen copias del mismo documento, si no que lo que se envía es la referencia al mismo. En el mundo del software, un Google Doc sería un reference type.
Veamos qué sucede a nivel software. Cuando escribimos:
Lo que ocurre es lo siguiente:
- Se reserva un espacio de memoria y se guarda el valor (
8
) en él. - Ese espacio de memoria tiene una dirección de memoria (supongamos que es
0x100111
). - Se crea un símbolo (
a
) que apunta a esa dirección de memoria (0x100111
) que guarda el valor asignado (8
).
El siguiente diagrama refleja lo que ocurre a nivel interno:
Si el objeto a
es un Reference Type
Imagina que tienes este código:
Lo que ocurre aquí es que se crea otro símbolo (b
) que apunta a la misma dirección de memoria que a
(0x100111
). Por lo tanto, ambas variables apuntan al mismo objeto. Se comparte.
Si modificamos b
y le damos el valor 18
, el objeto a
también tendría el valor 18
, ¡ya que son el mismo objeto!
Si el objeto a
es un Value Type
Si tenemos el mismo código pero a
es un value type, lo que ocurre es:
- Se reserva un espacio de memoria diferente (
0x001101
, por ejemplo) - Se copia el valor que tiene
a
(osea,8
) en ese nuevo espacio de memoria (0x001101
) - Se crea otro símbolo (
b
) que apunta al nuevo espacio de memoria (0x001101
)
En el siguiente diagrama se refleja lo que ocurre:
Como puedes comprobar, se ha hecho una copia y en el caso que modifiques b
con el valor 18
, el objeto a
quedaría intacto y no se vería afectado, siendo su valor el original, 8
.
Únete a la newsletter para no perderte ningún nuevo tutorial: recibe un email cuando se publiquen nuevos artículos o vídeos y no pierdas la oportunidad de seguir aprendiendo.
Stack VS Heap
Las Structs se crean en el Stack, mientras que las Classes lo hacen en el Heap.
Tanto Stack como Heap son estructuras de datos utilizadas para la reserva de espacios de memoria en el software. No voy a explicar en detalle las características de cada uno, ya que daría para otro post.
En el caso del Stack (o pila de llamadas) cabe destacar que su ejecución es inmediata, controlada por la CPU. Es muy eficiente y rápido. Funciona bajo el concepto de LIFO (last in first out), de ahí su rapidez y eficiencia.
El Heap (o almacenamiento libre) es una enorme pieza de memoria que el sistema puede soliciar reservar un trocito para su uso (mediante alloc
). Añadir o borrar memoria del heap es un proceso más costoso y pesado.
Inicializador por defecto
Las Structs crean un método init
por defecto con tantos parámetros como propiedades tenga. ¡Ojo!, porque en el momento que nosotros creemos un init
, el que se crea por defecto desaparece y tendríamos que añadirlo manualmente.
En el caso de las Classes tenemos que definir nosotros mismos el inicializador. Siempre. Por eso, cuando estamos definiendo una class, el compilador se queja desde el primer momento y nos dice algo así como: Class [Nombre de la clase] has no initializers (La clase [nombre de la clase] no tiene inicializadores).
Hablando de inicializadores, las clases tienen init
s de conveniencia, marcados con la palabra convenience
delante, mientras que las structs no.
Super-Truco: si en un Struct añades los
init
s dentro de una extensión, elinit
por defecto no se destruye y no tenemos que añadirlo de nuevo.
Inmutabilidad
En las structs TODO es inmutable por defecto. Para poder modificar un struct, hay que poner la palabra mutating
delante de la firma de la función. En el caso de las clases, aunque declares un objeto como constante (con let
), puedes modificar sus propiedades si estas están declaradas como var
.
Lo veremos con detalle en unos minutos en el Playground.
Curiosidad: la palabra
mutating
, en realidad, reemplaza el value type anterior por el nuevo. Es decir, en realidad no se modifica, sino que se crea uno nuevo con los datos modificados que sustituye al anterior.
Herencia, Type Casting y métodos deinit
Las Classes tienen herencia, es decir, puede tener cero o una superclase. Por ello, también se puede utilizar type casting con las clases. Como las classes se crean en el Heap, y es memoria que hay que crear y liberar, todas ellas tienen un método deinit
que se ejecuta justo antes de liberarse de memoria.
Las structs no tienen Herencia, ni type casting ni métodos deinit
.
Entonces, ¿cómo pueden añadirse super-poderes a las structs si no se puede utilizar la Herencia? Mediante composición, utilizando protocolos.
Manos al teclado: Xcode
Vamos a ver toda esta teoría aplicada en el código.
Primero, vamos a crearnos un nuevo tipo llamado Developer
, que representará a un desarrollador de software. Primero lo crearemos como una class, modificaremos alguna propiedad y veremos cómo se comporta. Luego haremos lo mismo pero utilizando un struct.
Como una clase
Podemos observar como el compilador nos obliga a crear un init
para darle valor a sus propiedades. En el caso de las structs, veremos que el compilador crea uno por defecto.
Ahora vamos a crear dos instancia de la clase Developer
, y ver qué le ocurre a la primera cuando modificas la segunda.
Aquí vemos dos características de las classes:
- A pesar de haber declarado la variable
xandre
como constante (utilizandolet
), después podemos modificar su propiedadlanguage
sin ningún problema. - Al tratarse de un Reference Type, tanto la original
alexandre
como la copiaxandre
apuntan al mismo objeto. Por lo tanto, si modificas cualquiera de ellas, la otra «ve» esos cambios y también se modifica. ¡Porque son el mismo objeto!
Como un struct
Podemos observar que:
- No tenemos que codificar el método
init
. El compilador crea uno por defecto. - Tenemos que crear la segunda variable con
var
para que el compilador nos deje modificar, posteriormente, su propiedadlanguage
. Por defecto, todo en una struct es inmutable. - Al tratarse de un Value Type, cuando modificamos la copia
xandre
, la originalalexandre
no se ve afectada en absoluto. ¡Ya que son objetos diferentes! Son copias distintas.
Cuándo escoger un struct o una class
Ahora que ya conocéis las diferencias fundamentales entre struct y class, os resultará más fácil escoger la opción correcta en cada situación.
En mi caso particular, utilizo structs para guardar datos. Siempre empiezo creando los modelos de mis apps como structs. Sólo en caso que necesite alguna característica concreta de las clases, como identidad o herencia, transformo el struct en una class.
Las classes las utilizo para crear los objetos «que hacen cosas». Aquéllos que no son simplemente datos. Por ejemplo, los ViewModels
, Presenters
, Coordinators
, Managers
, Controllers
, etc…
Dado que las structs se crean en el Stack (muy rápido) y las classes en el Heap (más lento), el factor velocidad también puede influir a la hora de seleccionar una u otra. Por ejemplo, si tengo que crear muchos objetos en un periodo de tiempo muy corto, es interesante hacerlo con structs.
Algo que debemos tener en cuenta es que Cocoa, el conjunto de frameworks que nos proporciona Apple para crear aplicaciones, está escrito (en su mayoría) en Objective-C. En este lenguaje no existen las structs de Swift, por lo que casi todas sus APIs están basadas en clases. Por ello es habitual verse forzado a utilizar clases, al menos hasta que en Cupertino reescriban Cocoa en Swift.
Conclusión
El lenguaje Swift nos da la posibilidad de utilizar structs o classes para crear nuevos tipos. Ambas sirven para lo mismo pero tienen características diferentes, por lo que dependiendo de la situación y las necesidades que tengamos, unas veces será ideal utilizar structs y otras, classes.
En este artículo hemos visto las principales diferencias entre ambos.
¿Y tú qué opinas? ¿Utilizas las dos? ¿Hay alguna diferencia fundamental que se me haya escapado? Déjame tus preguntas, comentarios o feedback en los comentarios. Estaré encantado de leeros.
Por cierto, me he abierto un Instagram hace poco, donde además del blog compartiré cosas más personales. ¿Nos seguimos?
Comparte 👇👇👇