Un problema
A medida que nos hemos ido familiarizando con el uso de objetos y especialmente cuando los hacemos disponibles por medio de variables de instancia, empezamos a tropezar con situaciones en las que nos gustaría acceder a un objeto (variable) desde fuera de su ámbito normal. Por ejemplo, podríamos querer pasar un objeto que contiene datos importantes desde una instancia-ventana a una instancia-informe o incluso a otra instancia-ventana diferente. Pero si pasamos la variable de objeto como parámetro, obtendremos una copia nueva e independiente de ese mismo objeto y puesto que se trata de un parámetro, solo alcanzable desde un ambito local. Si posteriormente copiamos el objeto sobre una nueva variable de instancia, con el fin de hacerlo disponible en ese ambito, estaremos duplicando de nuevo el objeto original, al final podría resultar que terminásemos con un sin fin de copias del mismo objeto.
Naturalmente, cualquier cambio que posteriormente realicemos sobre estas copias no será reflejado en el objeto original. Podemos decir que las copias pasan a tener vida propia tan pronto como son creadas. Por supuesto podemos compartir un objeto común mediante (por ejemplo) el uso de una variable de clase, ya que estaría disponible para todas las instancias generadas a partir de esa misma clase, por ejemplo, entre las varias instancias de la misma clase-ventana. Pero en ese caso ya no tendríamos el objeto a nivel de instancia, sino que afectaría a todas las diferentes instancias de la clase.
Una solución
Las variables "Object Reference" nos permiten crear un puntero hacia un objeto, eliminando la necesidad de realizar una nueva copia del mismo. Esto resulta en un uso más eficiente de los recursos de memoria RAM en nuestra aplicación y al mismo tiempo dotamos de una mayor coherencia a las acciones requeridas para con ese Objeto. Como veremos, también disponemos de algunas "utilidades" con las que realizar un seguimiento de las referencias o punteros en uso.
Pero no podemos simplemente crear referencias para las variables-objeto que tengamos creadas. Debemos tener en cuenta, que las variables-objeto son en realidad contenedores de los objetos instanciados y las "Object Reference" generan punteros hacia dichas instancias por lo que sólo podemos usarlos con instancias de objetos que han sido generadas de un modo algo diferente al habitual. Puesto que no se ha acuñado término alguno para éste tipo de instancias, las llamaremos "referenciables" o también "instancias refrenciadas".
Creación de un "Object Reference"
Ya sabemos que podemos generar dinámicamente instancias de un objeto mediante su método $new(), el valor devuelto por éste, es la instancia propiamente dicha. Podemos hacer esto mediante el comando "Do" o mediante "Calculate", tal como se observa en el siguiente ejemplo:
Do $objects.demoObject.$new(5) Returns demoObj
Calculate demoObj as $objects.demoObject.$new(5)
El "5" en estos ejemplos es un valor enviado como parámetro al método $construct de la instancia y que (como sabemos) es ejecutado automáticamente en el momento de su creación.
Por supuesto ésta no es la única forma de generar una instancia, (por ejemplo) podríamos hacerlo mediante definir una variable objeto (en el panel de definición de variables) y asignarle un valor como sub-tipo desde una clase en nuestra librería u otras opciones similares, pero en éste artículo optamos por la creación dinámica de instancias, a fin de que pueda observarse con mayor facilidad los elementos que son objeto de discusión.
Esencialmente, las variables "Object Reference" son asignadas bajo el mismo criterio o modo que las variables "Object". De hecho, desde la versión 4.0, existe en Omnis Studio un método creado específicamente con este propósito. El método $newref(), su uso:
Do $objects.demoObject.$newref(5) Returns objref
Calculate objref as $objects.demoObject.$new(5)
Al ejecutarse, este método se crean ambas cosas, es decir, un objeto y una referencia, se crea una nueva instancia de ese objeto y un valor de referencia que apunta hacia esa misma instancia. La instancia es creada directamente a partir de la clase, Omnis añadirá un carácter de subrayado y un número entero al componer el nombre de la instancia con el fin de que sea único y al mismo tiempo guarda un puntero hacia esa misma instancia, en la variable de tipo "Object Reference" especificada.
Ambos tipos de variables (Object y Object Reference) son iguales desde el punto de vista operacional, pero con una sutil y gran diferencia...
Cuando hacemos uso de "Object reference" para crear instancias, el Objeto "referenciable" (término con el que hemos bautizado a las instancias referenciadas), es decir la instancia continuará existiendo aún después de que su puntero o variable "Object reference" haya sido destruida. De hecho, perdemos contacto con dicha instancia (excepto si es a través del grupo $inst) a menos que nos aseguremos de tenerlo a buen recaudo.
Paso de "Object References"
Si queremos pasar un valor "Object reference" como parámetro, tendremos simplemente que especificar el nombre de la variable de tipo "Object reference" (que contiene el valor) como parámetro durante la llamada al método, igual que lo haríamos con cualquier otro. La variable-parámetro en el método de recepción, también deberá ser de tipo "Object reference". En su ejecución, dicha variable-parámetro constituirá otro punto de acceso a la misma instancia.
Si lo deseamos, también podemos duplicar la referencia sobre otra variable mediante el comando "Calculate", el método "$assign()" o bien mediante asignar su valor de retorno, a continuación mostramos los tres posibles modos:
Calculate dataObjRef2 as dataObjRef1
Do dataObjRef1 Returns dataObjRef2
Do dataObjRef2.$assign(dataObjRef1)
Note que (en éste caso) no podemos hacer uso del comando "Set reference", ya que sólo funciona con variables de tipo "Item reference" y evidentemente las "Object reference" no son "Item reference". La transmisión de valores deberá hacerse entre variables del mismo tipo.
Dado que diferentes celdillas de una variable de tipo lista, también se comportan como variables en si mismas, podemos hacer uso de las mismas técnicas para la asignación de valores, así un "Object reference", puede ser guardado en una celdilla. Por ejemplo, si se definió "listVar" para incluir una columna de tipo "Object reference" con el nombre "dataObjRef2" y queremos copiar la referencia de "dataObjRef1" sobre la celda de la línea 3, usaríamos:
Calculate listVar.3.dataObjRef2 as dataObjRef1
Tanto "dataObjRef1", como la celda en la lista, apuntan ahora a la misma instancia.
Un mayor poder, conlleva una mayor responsabilidad
Si usamos esta herramienta, deberemos también asumir la responsabilidad de mantener aseados nuestros propios líos (La versión 6.1 de Omnis permitirá otorgar cierto automatismo a ésta tarea). En su uso habitual con una variable de tipo "Object", la instancia correspondiente es destruida automáticamente tras eliminarse su variable, lo cual ocurre cuando el elemento (método, instancia o tarea) que lo la contiene se cierra, en estos casos, Omnis Studio gestiona esta tarea de limpieza por nosotros.
Pero cuando usamos una variable "Object reference" y luego la destruimos, la instancia del objeto referenciado al que señalaba continúa existiendo. Sólo se destruye su referencia. La instancia del objeto sigue viva en algún lugar de la memoria RAM, ya que podrían existir otras variables de tipo "Object reference" que aún la necesiten. Nos corresponde a nosotros escribir el código necesario para destruir la instancia y mantener limpio el espacio RAM. El único momento en que la instancia de un objeto referenciado es destruido automáticamente, es cuando a su vez la tarea que lo contiene es destruida o bien al salir de Omnis Studio.
Entonces, ¿cómo eliminar las instancias "huérfanas"? Podremos hacerlo mediante el método $deleteref(). Este método nos permitirá eliminar la instancia del objeto al que apunta la variable "Object reference" indicada. Su ejecución causará la llamada del método $destruct de la instancia, como parte del proceso. Exactamente del mismo modo que sucede con cualquier otra instancia.
No podemos aplicar el método $deleteref() directamente sobre la instancia, pero si disponemos de un modo para localizar las instancias-referenciadas, se trata del método $listrefs(). (Se ha de tener en cuenta que mediante este método obtendremos todas instancias-referenciadas, sea que estén "huérfanas" o no) Podremos usarlo en cualquier lugar de nuestra aplicación. Se nos devolverá una lista de todas las instancias-referenciadas, según el nivel del árbol de notación en que lo apliquemos, si es $root, será a nivel global, es decir todas las existentes en nuestra aplicación.
La lista constará de una sola columna con los valores "Object reference", sobre las que se podrá aplicar el método $deleteref(), para hacerlo sobre todo el conjunto, usaríamos el método $sendall(). El Manual de programación Omnis en castellano, proporciona algunos ejemplos útiles sobre su uso.
Si lo que quiséramos fuera hacer una copia, disponemos del método $copyref(). En éste caso, el valor de retorno del método será un nuevo "Object reference", al igual que ocurría al aplicar el método $newref() y al igual que éste, $copyref() generará una nueva instancia del objeto referenciado, se trata (por tanto) de un duplicado exacto (a excepción de su nombre, por supuesto) de la instancia a la que apunta las variable "Object reference" original.
En el ejemplo siguiente usamos "dataObjRef1" para crear un duplicado de la instancia a la que hace referencia, guardando en "dataObjRef2" un puntero hacia la recién creada:
Calculate dataObjRef2 as dataObjRef1.$copyref()
También podemos utilizar el comando "Do", con o sin el método "$assign()", para realizar la misma tarea. Se ha de tener en cuenta que las instancias creadas son completamente independientes entre sí, pero estará bien, si eso es lo que queremos. El posterior uso de "$listrefs()" sobre la clase de objeto original, mostraría (al menos) dos instancias referenciadas en la lista.
Pero ¿qué pasa si por error destruimos una instancia, cuando resulta que aún existía una variable "Object reference" apuntando hacia ella? ¿Cómo podemos saber si el "Object reference" que deseamos utilizar sigue siendo válido? No nos alarmemos, el método "$validref()", acude en nuestra ayuda, nos permite probar si la referencia es aún utilizable. Este método devuelve un valor booleano, si es kTrue significará que la referencia es válida.
Aunque bien podría considerarse una exageración utilizar este método antes de realizar cualquier acción que implique el uso de una variable "Object reference", habrá ocasiones en las que sea razonable realizar una comprobación de este tipo. Considere (por ejemplo) el caso en que haya recibido la referencia desde una fuente externa, si es así, es muy posible que deseemos comprobar si aún sigue siendo válida, supongamos que hemos programado en una ventana la ejecución del método "$deleteref()" en su "$destruct", cuando es el caso que existen otras ventanas aún abiertas que requieren de su uso, ocurriría que la referencia pasada a esta segunda ventana no sería válida.
Llegados a éste punto, les propongo desde "Aula Omnis" un ejercicio sencillo para demostrar que una instancia referenciable puede continuar vinculada. Estos son los pasos:
- En primer lugar, ejecutamos el método "$newref()" dentro de una tarea para generar una instancia referenciable y capturar su referencia en una variable de tipo "Object reference".
- A continuación, pasamos el "Object reference" a otra tarea y la testamos ($validref) para asegurarnos de que nos estamos comunicando con la instancia original.
- Luego destruimos la primera tarea, que a su vez destruirá la instancia referenciable que hemos generado desde dentro de ella.
- Desde la tarea que nos queda, testemos de nuevo la variable "Object reference" con el método "$validref()". No será válida, lo que significa que la instancia original fue destruida.
Si lo desea también podría construir una lista de todas las instancias mediante "$listrefs()" antes y después de la destrucción de la tarea.
(basado de un articulo de David Swain)