Cargando dinámicamente una clase

mayo 25, 2010

Siguiendo un poco con el tema del anterior post, en esta entrada vamos a ver como podemos cargar una clase de forma dinámica usando principalmente NSClassFromString.

Gracias a este mecanismo, podemos dejar para la ejecución la elección de la clase que vamos finalmente vamos a utilizar. Esto es bastante útil cuando estamos desarrollando, por ejemplo, un sistema de plugins, de tal manera que en tiempo de ejecución se cargará la clase que contiene el código del plugin. Cosa que en tiempo de compilación no conocemos.

Este mecanismo hace uso intensivo de dos funcionalidades de Objetive-C.

Una de ellas es la capacidad para mandar mensajes a los objetos sin que estos tengan que estar definidos previamente, que es precisamente lo que vimos en el anterior post, por lo que no entraré en mas detalles.

La otra caracteristica que es usada por la carga dinámica de clases es los tipos anónimos. Dichos tipos pueden contener cualquier tipo de instancia de clase. A priori podríamos caer en error de confundirlo con el void* de C/C++, sin embargo aunque comparte la caracteristica de “tipo genérico”, las implicaciones que hay detras del tipo anónimo de Objetive-C son más profundas.

El tipo anónimo se representa por la palabra clave “id“. Cuando declaramos una variable de tipo id, podemos enviarle cualquier mensaje, y si la instancia que hay detras de ese tipo es capaz de responder al mensaje, lo responderá. Ahondando en las diferencias con el void* de C++, usando id, no necesitamos hacer ningun cast para invocar a sus métodos. Lo cual aporta mucha seguridad a nuestro código, porque, como ya sabremos, se podría decir que los cast son de los mecanismos mas inseguros a la hora de desarrollar.

// C++
int myfunc(void* obj1)
{
    obj1->metodo1(); // !!!!! ERROR de compilación
    ((MiClase)obj1)->metodo1();
        // OK, pero si obj1 no es de tipo MiClase fallará al ejecutarse.
        // Bastante inseguro
}

// Obj-C
- (int) myfunc:(id) obj1
{
    [obj1 metodo1];
        // Compila perfectamente, si en tiempo de ejecucion obj1 no responde al mensaje metodo1.
        // no pasa nada grave excepto que no retorna nada. No es nada inseguro.
}

Como podemos intuir, el tipo id nos viene perfectamente “al pelo” para entender el funcionamiento de NSClassFromString(). Este método nos va a devolver un objeto de tipo Class que contiene la definición de la clase, cuando instanciamos esta clase el tipo que nos permite referenciar la instancia es “id“.
Realmente no sabemos de que tipo es, de hecho, gracias a la “magia” de Objetive-C no necesitamos tener ni el .h de la clase que estamos instanciando.

Este mecanismo existe sin duda en otros lenguajes como C/C++ o Java. Normalmente para hacer esto es requerido que la clase que estamos instanciando implemente alguna interfaz, de tal manera que referenciaremos a la instancia usando la propia interfaz. En objetive-C tambien podemos hacer esto, pero mientras que en los otros lenguajes es necesario, aquí, no lo es.

Para demostrar el funcionamiento vamos a ver un ejemplo. Tenemos dos clases que realizan una operación. Dichas clases son Adder y Substractor, como se puede adivinar, la operación de uno es la suma y la resta es la del otro.

@interface Adder : NSObject {
}
- (int) operate:(int) op1 with:(int) op2;
@end

@implementation Adder
- (int) operate:(int)op1 with:(int)op2 {
    return op1 + op2;
}
@end

@interface Substractor : NSObject {
}
- (int) operate:(int) op1 with:(int) op2;
@end

@implementation Substractor

- (int) operate:(int)op1 with:(int)op2 {
    return op1 - op2;
}
@end

La idea es que, en nuestro programa pidamos al usuario el nombre de la clase a cargar, y dinámicamente carguemos la clase y llamemos a su método “operate”

int main (int argc, const char * argv[]) {
    char buffer[300]; memset(buffer, 0, 300);
    fgets(buffer, 300, stdin);
    buffer[strlen(buffer)-1] = 0; // Quitamos el salto de linea

    NSString* classString = [NSString stringWithCString:buffer encoding:NSASCIIStringEncoding];
    int op1 = 5, op2 = 4;
    Class cl = NSClassFromString(classString);
    if ( cl != nil ) {
        id ad = [[cl alloc] init];
        printf("Operating %d with %d. Result: %d", op1, op2, [ad operate:op1 with:op2]);
    }
    else {
        printf("Error loading class");
    }
}

El resultado es el que esperamos, si tecleamos Adder se realizará una suma y si tecleamos Substractor se restarán ambos números.

Este ejemplo es bastante sencillo pero creo que ilustra el funcionamiento de la carga dinámica de clases. En Cocoa este mecanismo se utiliza bastante frecuentemente, por ejemplo la clase NSApplication lo utiliza para instanciar nuestra aplicación.


Objetive-C, un lenguaje dinámico

mayo 9, 2010

Si programamos para Mac o iPhone, seguramente el lenguaje que usaremos será Objetive-C. Muchas veces no reparamos en lo diferente que es este lenguaje de otros como C o Java.

Sin duda, el primer aspecto diferenciador es la sintaxis. Tanto en C, C++ o Java, estamos acostumbrados a invocar a los métodos de los objetos usando un “.”, en objetive c esto es bastante diferente. Quizá os habreis preguntado el porque de esta sintaxis, o quizá solo pensabais que era una sintaxis más, sin embargo, hay unas razones en ese comportamiento. Pero no vamos a adelantar acontencimientos 🙂

Objetive-C fue creado principalmente por Brad Cox and Tom Love ayá por los años 80, su principal intención era añadir caracteristicas del lenguaje Smalltalk al C tradicional.

Smalltalk por su parte fue creado en los años 70, y pese a su antiguedad fue de los primeros lenguajes que aplicaban mas profundamente la teoria de orientación a objetos. De hecho, aún hoy en día se sigue usando como modelo de enseñanza de este paradigma de programación.

Si queremos citar alguna de las caracteristicas que le destacaban como abanderado de la orientación a objetos podemos mencionar cosas como la instrospeccion de la que Smalltalk-80 podía alardear, o el paso de mensajes como forma de comunicación entre objetos.

Además en Smalltalk todo era un objeto, no existía los tipos básicos. Cualquier “integer” era a su vez un objeto al que se le podían enviar mensajes.

Volviendo a Objetive-C, estas son las caracteristicas que Cox y Love incorporaron, haciendo un lenguaje bastante diferente a los otros que estamos acostumbrados a usar.

Una de las cosas mas importantes donde se diferencia Objetive-C, y de la que vamos a hablar en el post, es en el dinamismo. Cuando hablamos de dinamismo nos estamos refiriendo a la forma de invocar los métodos, o mas correctamente, la forma de enviar mensajes a instancias.

Si nos fijamos, la sintaxis de Objetive C (heredada de Smalltalk) elimina el “.”, esto nos esta indicando que cuando estamos enviando un mensaje a un objeto, no exigimos en tiempo de compilación que el mensaje exista. Es decir, en Java, si llamamos a un método, dicho método debe existir durante la compilación, o esta fallará, en Objetive-C, esta comprobación no se hace más que para enseñar un warning.

Veamos un ejemplo.

// JAVA
// Definicion
...
int suma (int operando1, int operando2)
...

// Llamada
MiObjeto.suma(4, 5)
MiObjeto.resta(3, 2) // Fallo de compilacion, resta no existe

// Objetive-C
// Definicion
...
(int) sumaConOperando1:(int) op1 operando2:(int) op2
...

// Llamada
[MiObjeto sumaConOperando1:4 operando2:5]
[MiObjeto restaOperando1:3 operando2:2] // Compila OK, se lanza una excepcion al ejecutarse

Si MiObjeto no define el método, en Java no llegará a compilarse, en el caso de Objetive-C decimos que la clase no responde al mensaje, y el fallo se provocará en la ejecución.

Este detalle aporta un dinamismo a Objetive-C, que es dificil de encontrar en otro lenguaje compilado (excluimos los lenguajes de script).

A priori, puede parecer un impedimento, sin duda la mejor forma de evitar los errores es que se detecten antes de la ejecución, pero este comportamiento abre una nueva posibilidad a la hora de crear nuestro diseño de clases.

Cuando mandamos un mensaje a un objeto, dinamicamente se comprueba si este objeto puede responder a este mensaje, si no puede, se puede enviar este mensaje a otro objeto de la jerarquía, si nadie responde a ese mensaje, se lanzará la excepcion. Excepción que puede ser capturada sin que llegue a dañar a la ejecución. Además, es posible enviar el mismo mensaje a un grupo de objetos, de una forma que es difícil hacer en Java o C. Por ejemplo, podríamos iterar por una colección de objetos y, visto desde el punto de vista de Java, llamar a un mismo método de todos los objetos.

Este comportamiento es ampliamente usado en Cocoa, como por ejemplo cuando una aplicación se inicia, se les envia un mensaje de inicialización a todos los objetos que forman nuestra aplicación. Mensaje al que podemos responder, o no. Sin que este hecho, suponga un fallo en la aplicación. O por ejemplo, en la forma de implementar el mecanismo de Target-Action, del que ya hablamos en este post.

Sin embargo, todo este dinamismo tiene un precio, de esta manera, invocar un metodo en C++ es bastante mas rápido que enviar un mensaje en Objetive-C, ya que como se puede suponer, encontrar la dirección de memoria donde esta la función en C, es mucho más rápido que el mecanismo de paso de mensajes.

Desde el punto de vista del código, lo métodos se pueden manejar con lo que se conoce como “Selectors”, de esta manera el mensaje suma del que hablabamos antes se puede identificar como: “@selector(sumaConOperando1:operando2:)“.

Dentro de la clase NSObject existen numerosos mensajes que nos pueden ayudar a realizar tareas con selectors.

Por ejemplo con “performSelector“, podemos enviar el mensaje de la misma manera que lo hacemos por el nombre de la función. Por lo que, al ser el selector una “variable” podemos asignarla de forma dinámica, y así, decidir en tiempo de ejecución cual es el mensaje que vamos a enviar.

Ademas de “performSelector“, sin duda el mensaje más util de NSObjet es “respondsToSelector“, que nos va a decir si un objeto en concreto responde a un mensaje o no, de tal manera que nos podemos evitar el llamarlo, si sabemos que no va a responder.

Aparte de esta característica tan importante y central del lenguaje, Objetive-C se diferencia en muchas otras cosas de los lenguajes “tradicionales”, que perfectamente, pueden ser objetivo de otros posts. 🙂