Propiedades

Tutorial Swift – Propiedades

Josué V. Herrera Swift Básico, Tutoriales 0 Comments

En esta ocasión aprenderemos sobre las Propiedades y es curioso ya que cuando nos hablan por primera vez sobe las Propiedades en el lenguaje Swift tendemos a buscar similitudes en otros lenguajes… sigan leyendo este tutorial y se darán cuenta de como nos sorprende.

Propiedades almacenadas

En su forma más simple, una propiedad almacenada o de tipo es una constante o variable que se almacena como parte de una instancia de una clase o estructura particular. Las propiedades almacenadas pueden ser tanto propiedades variables (introducidas por la palabra clave var) o propiedades constantes (introducidas por la palabra clave let).

El siguiente ejemplo define una estructura llamada FixedLengthRange que describe una serie de números enteros cuyo rango no se puede cambiar una vez que se crea:

…en este ejemplo firstValue y length son propiedades, la primera de tipo Int al igual que la segunda, con la diferencia de que esta última es una constante, hasta aquí ambas representan todo cuanto esperamos (al menos yo :-D).

También pudiéramos definir a las propiedades de tipo como que son universales al tipo que conforman y el valor de estas se compartirá a travez de todas las instancias. Estas propiedades usualmente almacenan o se recomienda que almacenen información que siempre sea la misma. Por ejemplo: todas las instancias de un supuesto tipo Square (Cuadrado) tendrá siempre cuatro lados por lo que deberíamos de guardar este valor en una propiedad almacenada o de tipo, como también se le llama.

Algo que debemos hacer notar en el ejemplo anterior es que la variable length, propiedad de la estructura FixedLengthRange no es del todo variable, esta ha sido declarada como estática, no la hemos inicializado y aún así en la línea 8 le pasamos un valor en el segundo parámetro.

Primero tenemos que conocer una característica de las estructuras en Swift, y es que a diferencia de las clases no es necesario declarar un inicializador (init) o constructor como se le conoce en otros lenguajes de programación, el compilador crea uno por nosotros e inicializa sus miembros. Lo segundo es que cuando declaramos una variable constante no es obligatorio asignarle un valor, ya que el compilador nos permite hacer esto al momento de la inicialización de la instancia, es decir dentro de init, en el caso de las clases y de manera automática en las estructuras, luego de esto la clase queda sellada, a partir de aquí ya no se puede volver a asignar ningún valor a esta ni modificar el actual.

Instancias de estructuras constantes

Ya que estamos hablando de propiedades constantes, ¿qué sucede si creamos una instancia de una estructura y la asignamos a una constante? Pues no podemos modificar las propiedades de la instancia, incluso si estas fueron declaradas como variables, es decir con la palabra clave var, veamos un ejemplo:

…en la primera línea declaramos rangeOfFourItems como estática y la inicializamos con una instancia de FixedLengthRange para luego tratar de asignar el número 6 a la propiedad firstValue la cual es variable, aun así esta última línea nos generará un error, no compilará nuestro proyecto.

Este comportamiento responde a que las estructuras se pasan por valor, por lo que si el contenedor final es estático todo valor en él, también lo será aunque este contenga propiedades variables.

Veamos un ejemplo donde comprobemos la veracidad de todo esto:

…la salida en pantalla sería:

…hasta aquí todo bien, so far so good!

En este ejemplo estamos re-usando la estructura planteada hasta ahora, más una clase de apoyo, a ambas instancias le daremos el mismo trato y constataremos el comportamiento que antes les había comentado.

Comenzaremos modificando la línea 10, donde cambiaremos la palabra clave var por let y veremos que sucede, pues el compilador nos muestra el siguiente error:

…ahora volvamos a poner la línea 10 como estaba y cambiemos entonces la línea 31, de igual manera var por let, ahorita el mensaje que se nos muestra es el siguiente:

…exacto, no hay mensaje de error, rangeOfFourItems es una instancia estática que hace referencia a una clase que cuenta con propiedades estáticas y variables y estas seguirán comportándose como tal, la única limitante sería que rangeOfFourItems no podrá ser re-asignada.

Propiedades lazy

Lazy significa vago o perezoso en Ingles, por lo que estaríamos refiriéndonos a un matiz de las propiedades, algo así como a una propiedad perezosa, es evidente que esto nos muestra un cambio en el comportamiento de la misma. Así es, resulta que una propiedad perezosa o lazy (así me gusta más) es una propiedad cuyo valor inicial no se calcula la primera vez que se utiliza. Indicamos que una propiedad es lazy escribiendo el modificador lazy antes de su declaración.

Las propiedades lazy son útiles cuando el valor inicial de una propiedad depende de factores externos cuyos valores no se conocen hasta que la inicialización de una instancia se haya completado. Este tipo de propiedades también es útil cuando el valor inicial de una propiedad requiere de un alto costo computacional y que no debe realizarse a menos que se necesite.

El ejemplo siguiente utiliza una propiedad lazy para evitar la inicialización innecesaria de una clase que hipotéticamente consume muchos recursos. Este ejemplo define dos clases llamadas DataImporter y DataManager:

La clase DataManager tiene una propiedad llamada data, que se inicia con un arreglo vacío de tipo String. Aunque no se muestra el resto de su funcionalidad, el propósito de la clase DataManager es administrar y facilitar el acceso a este conjunto de datos String.

Parte de la funcionalidad de la clase DataManager es la capacidad de importar datos de un archivo. Esta funcionalidad es proporcionada por la clase DataImporter. En nuestro ejemplo ficticio diremos que es posible para una instancia de DataManager administrar sus datos sin jamás importar datos de un archivo, por lo que no hay necesidad de crear una nueva instancia de DataImporter al momento de inicializar la propia DataManager. En cambio, tiene más sentido crear la instancia de DataImporter siempre y cuando se utilice por primera vez.

Debido a que está marcada con el modificador lazy, sólo se crea la instancia DataImporter para la propiedad importer cuando esta sea accedida por primera vez, como por ejemplo, cuando su propiedad FileName es consultada:

Propiedades computadas

Además de las propiedades almacenadas, clases, estructuras y enumeraciones podemos también definir propiedades computadas, que en realidad no almacenan un valor. En su lugar, proporcionan un getter y un setter opcional para recuperar y establecer otras propiedades y valores indirectamente.

…la salida en pantalla sería:

En este ejemplo tenemos 3 estructuras, Point, Size y Rect. La primera representa un punto en el plano, la segunda un tamaño y la tercera representa una figura de dos dimensiones, en este caso un rectángulo.

En la línea 39 creamos una instancia del la estructura Rect, la cual es inicializada con una instancia de Point donde especificamos el punto origen de la figura, es decir el punto del plano donde la crearemos, seguido por el tamaño de la misma para lo cual nos apoyamos en la estructura Size. Por último esta instancia es almacenada en la variable square (cuadrado) ya que pasamos en el segundo parámetro los dos lados iguales.

De la línea 18 a la 35 tenemos la propiedad computada center. En la línea 18 vemos que especificamos el tipo de dato de la propiedad y esto es obligatorio, siempre que vayamos a declarar una propiedad computada tenemos que declarar el tipo de dato que se devolverá en el bloque get y la que receptará el bloque set, en este caso de tipo Point.

Seguimos en la línea 41 donde creamos una variable llamada initialSquareCenter donde almacenamos la posición original de nuestra figura. Esta variable se iguala con la expresión square.center la cual se apoya en el punto (.) para acceder a la propiedad computada center, que ha su vez hace una llamada al bloque get el cual devuelve el centro de nuestra figura en formato de tipo Point.

Luego en la línea 43 hacemos uso de la expresión square.center pero esta vez se iguala a la expresión computada center con el valor Point(x: 15.0, y: 15.0) que hace una llamada al bloque set , donde se nombra el valor pasado como newCenter. Importante aclarar aquí, que en caso de que esto no se haya especificado en la línea 28, el nombre de este valor por defecto que le da el compilador sería de newValue. Este valor introducido es computado y con esto la figura es movida 10 unidades hacia arriba y 10 unidades hacia la derecha como podemos observar en la siguiente imagen:

Movimiento Lineal

Veamos otro ejemplo, este es mucho más sencillo así que no lo comentaré:

…la salida en pantalla sería:

Antes de finalizar este tópico quisiera mencionar que también disponemos de propiedades computadas de solo lectura (Read-Only) y que son aquellas que poseen solamente un bloque get, así de simple, en el ejemplo anterior con solamente eliminar el bloque set ya la estableceríamos de solo lectura.

Observadores

Los observadores son una característica más que poseen las propiedades y que observan y reaccionan a cambios en los valores de las mismas. Un observador es llamado cada vez que se asigna un nuevo valor a la propiedad, incluso cuando este valor es idéntico al anterior, por lo que se pudiera resumir en que cada vez que ocurre un cambio en el valor que este almacena el observador es notificado.

Podemos añadir observadores a cualquier propiedad que definamos a excepción de aquellas que establezcamos como lazy. De igual manera a las propiedades heredadas (ya sean almacenadas o computadas) también podemos añadirles observadores re-implementando la misma mediante una subclase.

Una propiedad puede contar con uno o ambos de los observadores siguientes:

  • willSet (es llamado justo antes de que el valor sea almacenado)
  • didSet (es llamado inmediatamente después de que el nuevo valor sea almacenado)

Si usted implementa el observador willSet el nuevo valor será pasado como constante, también y al igual que con los bloques set de las propiedades computadas, si no especifica de manera explícita un nombre para estos valores el compilador le dará uno por defecto, en el caso del observador willSet este nombre será newValue.

En el caso del observador didSet el valor antiguo será pasado al observador como constante y si no se especifica un nombre para este valor el nombre por defecto será oldValue.

Veamos un ejemplo de estos observadores en acción:

…la salida en pantalla:

La clase StepCounter asumamos que es parte de un software de deporte que mediante sensores en la zapatilla nos va contabilizando varios datos, en el caso específico de nuestra clase esta se encarga de llevar el total de pasos. La clase está declarada de las líneas 3 a la 24 y solamente cuenta con una propiedad, llamada totalSteps y definida de la línea 5 a la 22, es de tipo entero y con un valor por defecto de cero.

En el bloque willSet lo primero que hacemos es establecer un nombre para el valor que manejaremos y ejecutamos una acción, en este caso imprimimos un mensaje informando el nuevo valor a añadir. El observador didSet mantiene el nombre por defecto que el compilador asigna, ya que no hemos especificado otro de manera explícita como lo hicimos en el bloque anterior. En este bloque verificamos si el total es mayor que la cantidad anterior y si esto es cierto imprimimos un mensaje informando sobre la cantidad que ha sido añadida producto de la resta del nuevo total con el total antiguo.

En la línea 26 creamos una instancia de esta clase y en la 28 modificamos el valor total de pasos a 200 y de igual manera los vamos incrementando en las siguientes líneas. En paralelo con esto vemos como los observadores han ido registrando todos estos cambios mediante sus mensajes en la salida en pantalla.

Propiedades estáticas

Las propiedades también pueden ser declaradas como estáticas, permitiéndonos hacer llamadas a estas sin necesidad de instanciar la clase o estructura a la que pertenezca. Al igual que en otros lenguajes de programación esto lo podemos lograr con la palabra clave static. Aquí un ejemplo:

…la salida en pantalla:

En este ejemplo tenemos una estructura, una enumeración y una clase, en cada una de estas tenemos propiedades almacenadas y computadas declaradas como estáticas. Luego de la línea 45 a la 53 hacemos uso de todas estas propiedades sin necesidad de crear una instancia para cada uno de estos objetos.

Por si se han preguntado la palabra clave class en la línea 37 no es un error, esto nos permite que la propiedad computada overrideableComputedTypeProperty pueda ser sobre-escrita desde una subclase.

Espero que todo cuanto se ha dicho aquí, de una forma u otra le haya servido de aprendizaje, de referencia, que haya valido su preciado tiempo.

Este artículo, al igual que el resto, será revisado con cierta frecuencia en pos de mantener un contenido de calidad y actualizado.

Cualquier sugerencia, ya sea errores a corregir, información o ejemplos a añadir será, más que bienvenida, necesaria!