5 diferencias entre un Struct y una Class en Swift

¿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 que tienen entre sí, 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 demasiada diferencia entre crear una class o un struct.

¿Es eso correcto?

Sí y no.

¿Qué los diferencia?

Aunque los dos sirven para lo mismo, internamente sí tienen algunas diferencias importantes.

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.

 

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 inits de conveniencia, marcados con la palabra convenience delante, mientras que las structs no.

Super-Truco: si en un Struct añades los inits dentro de una extensión, el init 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. 

Aquí vemos dos características de las classes:

  1. A pesar de haber declarado la variable xandre como constante (utilizando let), después podemos modificar su propiedad language sin ningún problema. 
  2. Al tratarse de un Reference Type, tanto la original alexandre como la copia xandre 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:

  1. No tenemos que codificar el método init. El compilador crea uno por defecto. 
  2. Tenemos que crear la segunda variable con var para que el compilador nos deje modificar, posteriormente, su propiedad language. Por defecto, todo en una struct es inmutable.
  3.  Al tratarse de un Value Type, cuando modificamos la copia xandre, la original alexandre 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.

¡Gracias por leer!

Por: Alexandre Freire

Post original escrito en alexandrefreire.com

Alexandre es Software developer e instructor iOS en el Mobile Bootcamp Engineering de 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

Si tienes algo que deseas compartir o quieres formar parte de JustCodeIt, escríbenos a [email protected]



Share this:

Leave a comment