Cargando dinámicamente una clase

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.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: