Home » Guías » Patrones de diseño de software (II): Singleton
Singletons

Patrones de diseño de software (II): Singleton

Seguimos con nuestros especiales sobre patrones de diseño de software, la clave sobre la que vertebramos cualquier programa o app que realicemos y que, en cierto modo, será responsable de la eficiencia o no de nuestro código. Tras la primera entrega donde hablamos del patrón fachada o facade, ahora pasamos a uno patrón muy popular entre todos aquellos que desarrollamos en Objective-C: el singleton.

El singleton es un tipo de patrón de diseño presente en Objective-C o Java, por ejemplo, basado en la mecánica propia de la orientación a objetos. Lo que hace, básicamente, es mantener un objeto de una clase vivo durante todo el transcurso del ciclo de vida de la ejecución desde la primera que se instancia la misma.

En matemáticas, un singleton es una secuencia de un solo elemento o un conjunto unitario, es decir, con un solo miembro en el conjunto. Y esa es la función básica de una clase de tipo singleton, que solo produce un único objeto: siempre el mismo objeto.

Objeto persistente

¿Pero cómo consigue el lenguaje hacer persistente un objeto instanciado a partir de una clase? Pues basándose en la propia mecánica de funcionamiento de los punteros. Nosotros, normalmente, cuando creamos una clase y la instanciamos para crear un objeto, lo que hacemos es recibir un puntero hacia donde habita el que acabamos de instanciar (su dirección de memoria). Ese contenido es el que se almacena en la variable donde guardemos la instanciación.

Singleton

Cada vez que instanciamos una clase típica (como vemos en el gráfico superior) se nos devuelven diferentes objetos. La primera vez el objeto A, la segunda B y así sucesivamente. Pero una clase que usa el patrón singleton no actúa así. Lo que hace es modificar su inicialización en dos fases:

  1. Se instancia e inicializa almacenando el puntero del objeto resultante.
  2. Las siguientes veces que la instanciamos nos devuelve el puntero previamente almacenado: es decir, que cada vez que instanciamos la clase singleton siempre nos devuelve el puntero al objeto A y no uno nuevo como se hace normalmente.

El propósito es tener métodos de uso general y, sobre todo, tener propiedades persistentes más allá de la vida de la clase donde estamos instanciando un objeto. Este es el patrón que usan clases con las que trabajamos normalmente en Cocoa como NSFileManager o NSUserDefaults, asociados a un inicializador que suele llevar el término compartido (shared) dentro de su nombre, como sharedFileManager o standardUserDefaults.

De hecho, lo común es que usemos este tipo de inicializadores y no el de por defecto, con el objeto de usar este método de inicialización como un acceso directo al objeto que ya está vivo y del que podemos leer propiedades en cualquier momento de la vida de nuestro programa o app, aunque la clase donde usamos el singleton cuando le dimos valores a sus propiedades ya no exista.

Modelos de singletons en Swift

El usar este patrón en Swift es algo optativo, ya que también podemos usar funciones o propiedades globales en cualquier archivo de un proyecto, y podremos acceder a ellas desde cualquier otro. De igual forma, si decidimos usar este patrón para tener acceso a propiedades y métodos encapsulados en un mismo contenedor, tenemos que entender que este es compatible con cualquier otro patrón ya que, aunque se denomine patrón de diseño, podríamos decir que es una solución de persistencia o globalidad de una clase.

La forma más simple de usar un singleton es almacenando en una variable de tipo static dentro de la propia clase, una instanciación de sí misma, sin necesidad de usar más inicializadores. Por ejemplo:

Este código hará que la primera vez que instanciemos la clase Singleton a través de su propiedad sharedInstance, la clase pase por el método init() haciendo aquello que le indiquemos. Como esa primera vez, Singleton() no existe, por eso la inicializa en una llamada recursiva. Una vez almacenada, simplemente guardamos el valor static que ya apunta al objeto que creamos la primera vez.

Podemos ver con este ejemplo, como la constante A se inicializa con la propiedad propiedad a 0 (que es como se define en su código), pero cuando ponemos propiedad de A a valor 30, nos encontramos que al guardar en b de nuevo el singleton y preguntar por el valor de propiedad nos devuelve el que cambiamos en A, ya que ese momento A y b apuntan al misma objeto, cuya dirección está almacenada en la propiedad static sharedInstance.

El secreto para que esto funcione está en la propiedad de tipo static: una propiedad compartida entre todos los objetos de la clase que no puede ser sobrescrita por subclases. Este tipo de propiedad, conocida como propiedad de tipo, está presente en todos los objetos que se instancien de la clase porque como tal, no pertenece a la instanciación que genera objetos si no al propio tipo que estamos definiendo con la clase: por eso su valor es el mismo siempre. Un cambio de dicho valor en el objeto A, C o D de la misma clase, cambia el valor de la propiedad en todos ellos a la vez (porque está asociado al tipo Singleton y no al objeto A o b).

Por eso, sea cual sea el objeto de tipo Singleton donde consultemos sharedInstance siempre tendrá el mismo valor: en este caso el puntero que apunta al primer objeto que se instanció de ese tipo.

De hecho, hemos puesto el ejemplo de poner una propiedad y un método init por verlo más claro, pero el singleton, como tal, es solo la clase con la variable static.

No obstante, para aquellos que vienen de Objective-C, podríamos usar una aproximación más afín a este lenguaje, usando la instrucción dispatch_once que ejecuta un bloque una única vez en todo el ciclo de vida de la ejecución.

En este caso, estaríamos creando una propiedad de tipo class dentro de nuestra clase, cuyo tipo es la propia clase Singleton. Las propiedades de tipo class son propiedades de tipo, al igual que las static, con la diferencia que estas si pueden ser sobrescritas en caso que lo necesitemos. Y ambas, tanto static como class, al ser propiedades del tipo que definimos y no propiedades unidas a la instanciación, pueden ser invocados directamente en el tipo sin tener que crear objeto alguno con ellas, tal como hacemos al llamar a Singleton.sharedInstance para inicializar el singleton.

El usar class en vez de static (ambas servirían si lo probamos) es por ejemplificar ambos casos y que, en caso que quisiéramos, podríamos tener un hijo de la clase Singleton que podría cambiar para que el sharedInstance fuera el del hijo y no el del padre.

Al invocar Singleton.sharedInstance este se encarga de crear un struct interno que almacena el token de ejecución única para que así el sistema identifique el código y pueda saber si ya se ha ejecutado una vez, unido a otra propiedad instance que almacene (como opcional) la dirección del objeto instanciado la primera vez.

Luego, la instrucción dispatch_once intentará ejecutar usando el ID del identificador dentro del struct llamado Static, propiedad onceToken y este hará una inicialización de la propiedad instance del struct.

En resumen, hacemos lo mismo pero con más líneas de código. Ambas implementaciones funcionan, y solo hay que usar el ejemplo que hemos dado para comprobarlo. Obviamente, la primera de las soluciones es la más eficiente y elegante.

Conclusiones

La conclusión es simple: un singleton permite que una clase tenga persistencia en cuanto a aquello que almacenan sus propiedades. Y también consigue que los métodos se comporten de formas similares porque las propiedades que usan no se modifican entre instanciación e instanciación.

Os aconsejamos, como siempre, que los probéis, trabajéis con ellos y si tenéis cualquier duda, podéis acudir a nosotros. De hecho, aprovechamos para agradecer a AJPallares por su comentario donde nos ha matizado algunos asuntos que hemos incorporado al artículo para hacerlo más entendible en referencia a las propiedades de tipo. Apple Coding también es vuestra, así que os invitamos a participar cuando queráis. Hasta la próximo y Good Apple Coding.

Acerca de Julio César Fernández

Analista, consultor y periodista tecnológico, desarrollador, empresario, productor audiovisual, actor de doblaje e ingeniero de vídeo y audio.

Otras recomendaciones

Drag & Drop

Drag & Drop (I): Introducción y Drag

Iniciamos una serie de guías que explican todo lo presentado en la WWDC y cómo usarlo o cómo funciona. Explicado, esta vez, con nuestro primer screencast, un vídeo explicativo y práctico con Xcode 9, que te ayudará a seguir los pasos para implementar el drag de una app a otra y entender cómo está configurado y funciona. No os olvidéis de suscribiros a nuestro canal de Youtube si queréis ver más contenidos.

  • César Palma

    Julio César, de nuevo otro estupendo artículo. Muchas gracias por compartir tu conocimiento.

  • AJPallares

    Hola Julio, en primer lugar, enhorabuena por tu artículo y por el trabajo que realizáis.

    Leyéndolo me he dado cuenta de lo que creo, puede ser un pequeño error (o si no, puede llevar a malentendidos). Es por el párrafo donde se explica que una propiedad con la keyword “static” es una propiedad compartida entre todos los objetos de la clase que no puede ser sobrescrita por subclases.

    Sin embargo, si no me equivoco, las propiedades “static” es una propiedad de tipo y no una propiedad de instancia. Las únicas diferencias con las propiedades tipo “class” es que “class” solo están permitidas para tipos de clases y pueden ser sobrescritas por las subclases. “static” puede usarse en “struct” o “enum” y no se pueden sobrescribir.

    Al explicar la propiedades del tipo “class” apuntas que pueden ser usadas directamente desde la definición de la clase y no necesitan que haya un objeto instanciado de la misma. Pero es que las del tipo “static” funcionan exactamente igual.

    De hecho en el ejemplo aparece “Singleton.sharedInstance”, es decir, se accede a la “sharedInstance” desde la propia clase “Singleton” (y no desde una instancia de la clase).

    Espero haberme explicado de forma clara (porque concisa, veo que no).

    Saludos!