¿Utilizar un Struct o una Class? ¿Una Class o un Struct? He ahí la cuestión.
Hoy voy a explicarte las diferencias entre un Struct y una Class en Swift, y acabar por fin con la duda de no saber cuál escoger.
⬇️ También puedes ver este tutorial en video más abajo ⬇️
Además, esta es una pregunta muy típica en entrevistas iOS para perfiles junior, así que hoy matamos dos pájaros de un tiro.
¿Qué tienen en común?
Antes de hablar de las diferencias entre struct and class, hablemos primero de lo que tienen en común:
- Ambos mecanismos se utilzan 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 demasiadas diferencias entre un Struct y una Class en Swift a la hora de crearlos.
¿Es eso correcto?
Sí y no.
¿Cuál es la diferencias entre un Struct y una Class en Swift?
Aunque los dos sirven para lo mismo, internamente sí tienen algunas diferencias importantes entre structs vs class.
Value type VS Reference type
Entre las diferencias entre un Struct y una Class en Swift, 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 con esto de las diferencias entre un Struct y una Class en Swift, 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
.
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 struct en swift 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
Esta es otra de las diferencias entre un Struct y una Class en Swift, 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, con las diferencias entre un Struct y una Class en Swift.
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.
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 entre un Struct y una Class en Swift, 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 en las diferencias entre un Struct y una Class en Swift, 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, por eso es importante saber las diferencias entre un Struct y una Class en Swift. 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 struct class.
¿Y tú qué opinas? ¿Utilizas las dos? ¿Hay algunas diferencias entre un Struct y una Class en Swift fundamentales que se me haya escapado? Déjame tus preguntas, comentarios o feedback en los comentarios. Estaré encantado de leeros.
¡Gracias por leer!
Por: Alexandre Freire
Post original escrito en Swift
Alexandre es Software developer e instructor iOS en el Mobile Bootcamp Engineering d KeepCoding.
Aunque puedas haberlo visto impartiendo cursos de Android o de Ruby On Rails en el pasado, ahora está completamente enfocado en el desarrollo iOS, publicando artículos en su página web, o subiendo videos a su canal de YouTube. Alexandre vendió su propia startup (Apparcar) cuando tenía tan sólo 25 años, y según palabras textuales, su intención es «que no sea la última».
También puedes seguir a Alexandre en Twitter en @xandrefreire