Objective-C | sintaxis

Sintaxis

Objective-C consiste en una capa muy fina situada por encima de C, y además es un estricto superconjunto de C. Esto es, es posible compilar cualquier programa escrito en C con un compilador de Objective-C, y también puede incluir libremente código en C dentro de una clase de Objective-C.

Esto es, para escribir el programa clásico "Hola Mundo" para correr en consola, se puede utilizar el siguiente código:

#import <stdio.h>
int main( int argc, const char *argv[] ) {
    printf( "Hola Mundo\n" );
    return 0;
}

El código anterior se diferencia de un código en C común por la primera instrucción #import, que difiere del #include del C clásico, pero la función printf("") es puramente C. La función propia de Objective-C para imprimir una cadena de caracteres en consola es NSLog(@""); utilizándola, el código anterior quedaría de la siguiente manera:

int main( int argc, const char *argv[] ) 
{
    NSLog( @"Hola Mundo\n" );
    return 0;
}

La sintaxis de objetos de Objective-C deriva de Smalltalk. Toda la sintaxis para las operaciones no orientadas a objetos (incluyendo variables primitivas, pre-procesamiento, expresiones, declaración de funciones y llamadas a funciones) son idénticas a las de C, mientras que la sintaxis para las características orientadas a objetos es una implementación similar a la mensajería de Smalltalk.

Mensajes

El modelo de programación orientada a objetos de Objective-C se basa en enviar mensajes a instancias de objetos. Esto es diferente al modelo de programación al estilo de Simula, utilizado por C++ y esta distinción es semánticamente importante. En Objective-C uno no llama a un método; uno envía un mensaje, y la diferencia entre ambos conceptos radica en cómo el código referido por el nombre del mensaje o método es ejecutado. En un lenguaje al estilo Simula, el nombre del método es en la mayoría de los casos atado a una sección de código en la clase objetivo por el compilador, pero en Smalltalk y Objective-C, el mensaje sigue siendo simplemente un nombre, y es resuelto en tiempo de ejecución: el objeto receptor tiene la tarea de interpretar por sí mismo el mensaje. Una consecuencia de esto es que el mensaje del sistema que pasa no tiene chequeo de tipo: el objeto al cual es dirigido el mensaje (conocido como receptor) no está inherentemente garantizado a responder a un mensaje, y si no lo hace, simplemente lo ignora y retorna un puntero nulo.

Enviar el mensaje method al objeto apuntado por el puntero obj requeriría el siguiente código en C++:

obj->method(parameter);

mientras que en Objective-C se escribiría como sigue:

[obj method:parameter];

Ambos estilos de programación poseen sus fortalezas y debilidades. La POO al estilo Simula permite herencia múltiple y rápida ejecución utilizando vinculación en tiempo de compilación siempre que sea posible, pero no soporta vinculación dinámica por defecto. Esto fuerza a que todos los métodos posean su correspondiente implementación, al menos que sean virtuales (aun así, se requiere una implementación del método para efectuar la llamada). La POO al estilo Smalltalk permite que los mensajes no posean implementación - por ejemplo, toda una colección de objetos pueden enviar un mensaje sin temor a producir errores en tiempo de ejecución. El envío de mensajes tampoco requiere que un objeto sea definido en tiempo de compilación. (Ver más abajo la sección tipado dinámico) para más ventajas de la ligadura dinámica.

Sin embargo, se debe notar que debido a la sobrecarga de la interpretación de los mensajes, un mensaje en Objective-C toma, en el mejor de los casos, tres veces más tiempo que una llamada a un método virtual en C++.[2]

Interfaces e implementaciones

Objective-C requiere que la interfaz e implementación de una clase estén en bloques de código separados. Por convención, la interfaz es puesta en un archivo cabecera y la implementación en un archivo de código; los archivos cabecera, que normalmente poseen el sufijo .h, son similares a los archivos cabeceras de C; los archivos de implementación (método), que normalmente poseen el sufijo .m, pueden ser muy similares a los archivos de código de C.

Interfaz

La interfaz de la clase es usualmente definida en el archivo cabecera. Una convención común consiste en nombrar al archivo cabecera con el mismo nombre de la clase. La interfaz para la clase Clase debería, así, ser encontrada en el archivo Clase.h.

La declaración de la interfaz de la forma:

@interface classname : superclassname 
{
    // instance variables
}
+classMethod1;
+(return_type)classMethod2;
+(return_type)classMethod3:(param1_type)parameter_varName;

-(return_type)instanceMethod1:(param1_type)param1_varName :(param2_type)param2_varName;
-(return_type)instanceMethod2WithParameter:(param1_type)param1_varName andOtherParameter:(param2_type)param2_varName;
@end

Los signos más denotan métodos de clase, los signos menos denotan métodos de instancia. Los métodos de clase no tienen acceso a las variables de la instancia.

Si usted viene de C++, el código anterior es equivalente a algo como esto:

class classname : superclassname 
{
  public:
    // instance variables
    
    // Class (static) functions
    static void* classMethod1();
    static return_type classMethod2();
    static return_type classMethod3(param1_type parameter_varName);
    
    // Instance (member) functions
    return_type instanceMethod1(param1_type param1_varName, param2_type param2_varName);
   return_type instanceMethod2WithParameter(param1_type param1_varName, param2_type param2_varName = default);
};

Note que instanceMethod2WithParameter demuestra la capacidad de nombrado de parámetro de Objective-C para la cual no existe equivalente directo en C/C++.

Los tipos de retorno pueden ser cualquier tipo estándar de C, un puntero a un objeto genérico de Objective-C, o un puntero a un tipo específico así como NSArray *, NSImage *, o NSString *. El tipo de retorno por defecto es el tipo genérico id de Objective-C.

Los argumentos de los métodos comienzan con dos puntos seguidos por el tipo de argumento esperado en los paréntesis seguido por el nombre del argumento. En algunos casos (por ej. cuando se escriben APIs de sistema) es útil agregar un texto descriptivo antes de cada parámetro.

-(void) setRangeStart:(int)start End:(int)end;
-(void) importDocumentWithName:(NSString *)name withSpecifiedPreferences:(Preferences *)prefs beforePage:(int)insertPage;

Implementación

La interfaz únicamente declara la interfaz de la clase y no los métodos en sí; el código real es escrito en la implementación. Los archivos de implementación (métodos) normalmente poseen la extensión .m.

@implementation classname
+classMethod {
    // implementation
}
-instanceMethod {
    // implementation
}
@end

Los métodos son escritos con sus declaraciones de interfaz. Comparando Objective-C y C:

-(int)method:(int)i 
{
    return [self square_root: i];
}
int function(int i) 
{
    return square_root(i);
}

La sintaxis admite pseudo-nombrado de argumentos.

-(int)changeColorToRed:(float)red green:(float)green blue:(float)blue

[myColor changeColorToRed:5.0 green:2.0 blue:6.0];

La representación interna de éste método varía entre diferentes implementaciones de Objective-C. Si myColor es de la clase Color, internamente, la instancia del método -changeColorToRed:green:blue: podría ser etiquetada como _i_Color_changeColorToRed_green_blue. La i hace referencia a una instancia de método, acompañado por los nombres de la clase y el método, y los dos puntos son reemplazados por guiones bajos. Como el orden de los parámetros es parte del nombre del método, éste no puede ser cambiado para adaptarse al estilo de codificación.

De todos modos, los nombres internos de las funciones son raramente utilizadas de manera directa, y generalmente los mensajes son convertidos a llamadas de funciones definidas en la librería en tiempo de ejecución de Objective-C – el método que será llamado no es necesariamente conocido en tiempo de vinculación: la clase del receptor (el objeto que envió el mensaje) no necesita conocerlo hasta el tiempo de ejecución.

Instanciación

Una vez que una clase es escrita en Objective-C, puede ser instanciada. Esto se lleva a cabo primeramente alojando la memoria para el nuevo objeto y luego inicializándolo. Un objeto no es completamente funcional hasta que ambos pasos sean completados. Esos pasos típicamente se logran con una simple línea de código:

MyObject * o = [[MyObject alloc] init];

La llamada a alloc aloja la memoria suficiente para mantener todas las variables de instancia para un objeto, y la llamada a init puede ser anulada para establecer las variables de instancia con valores específicos al momento de su creación. El método init es escrito a menudo de la siguiente manera:

-(id) init 
{
    self = [super init];
    if (self) 
    {
        ivar1 = '''value1''';
        ivar2 = value2;
        .
        .
        .
    }
    return self;
}

Protocolos

Objective-C fue extendido en NeXT para introducir el concepto de herencia múltiple de la especificación, pero no la implementación, a través de la introducción de protocolos. Este es un modelo viable, ya sea como una clase base abstracta multi-heredada en C++, o como una "interfaz" (como en Java o C#). Objective-C hace uso de protocolos ad-hoc, llamados protocolos informales, y el compilador debe cumplir los llamados protocolos formales.

Tipado dinámico

Objective-C, al igual que Smalltalk, puede usar tipado dinámico: un objeto puede recibir un mensaje que no está especificado en su interfaz. Esto se permite para incrementar la flexibilidad, ya que permite a un objeto "capturar" un mensaje y enviarlo a otro objeto diferente que pueda responder a ese mensaje apropiadamente, o del mismo modo reenviar el mensaje a otro objeto. Este comportamiento es conocido como reenvío de mensajes o delegación (ver más abajo). Alternativamente, un manejo de error puede ser usado en caso de que el mensaje no pueda ser reenviado. Si un objeto no reenvía un mensaje, lo responde o maneja un error entonces el sistema generará una excepción en tiempo de ejecución. Si los mensajes son enviados a nil (el puntero de objetos nulo), serán ignorados silenciosamente o elevarán una excepción genérica, dependiendo de las opciones del compilador.

La información tipada estáticamente puede ser añadida opcionalmente a variables. Esta información es luego comprobada a la hora de compilar. En las siguientes cuatro declaraciones se proveen tipos de información crecientemente específicos. Estas declaraciones son equivalentes en el tiempo de ejecución, pero la información adicional permite al compilador el avisar al programador si el argumento pasado no encaja con el tipo especificado.

- (void)setMyValue:(id)foo;

En la declaración anterior, foo puede ser de cualquier clase.

- (void)setMyValue:(id<NSCopying>)foo;

En la declaración anterior, foo puede ser una instancia de cualquier clase que satisfaga al protocolo NSCopying.

- (void)setMyValue:(NSNumber *)foo;

En la declaración anterior, foo debe ser una instancia de la clase NSNumber.

- (void)setMyValue:(NSNumber<NSCopying> *)foo;

En la declaración anterior, foo debe ser una instancia de la clase NSNumber, y debe satisfacer al protocolo NSCopying.


Reenvío

Objective-C permite el envío de un mensaje a un objeto que puede no responder. En lugar de responder o simplemente ignorar el mensaje, un objeto puede reenviar el mensaje a otro objeto que pueda responderlo. El reenvío puede ser usado para simplificar la implementación de ciertos patrones de diseño, como el observer o el proxy.

El tiempo de ejecución de Objective-C especifica un par de métodos en Object:

  • métodos de reenvío:
- (retval_t)forward:(SEL)sel args:(arglist_t)args; // con GCC
- (id)forward:(SEL)sel args:(marg_list)args; // con sistemas NeXT/Apple
  • métodos de acción:
- (retval_t)performv:(SEL)sel args:(arglist_t)args; // con GCC
- (id)performV:(SEL)sel args(marg_list)args; // con sistemas NeXT/Apple

Un objeto que desee implementar el reenvío solamente necesita sobreescribir el método de reenvío con un nuevo método que defina el comportamiento de reenvío. El método de acción performv:: no necesita ser sobreescrito, ya que este método meramente realiza una acción basada en el selector y los argumentos. El tipo SEL es el tipo de mensajes en Objective-C.

Nota: en openStep, Cocoa y GNUstep, los espacios de trabajo de Objective-C comúnmente usados, no hay que usar la clase Object. el método - (void)forwardInvocation:(NSInvocation *)anInvocation de la clase NSObject es usado para realizar el reenvío.

Ejemplo

Aquí hay un ejemplo de un programa que demuestra las bases del reenvío.

Forwarder.h
# import <objc/Object.h>

@interface Forwarder : Object {
 id recipient; // El objeto al que queremos reenviar el mensaje.
}

// Métodos de acceso
- (id)recipient;
- (id)setRecipient:(id)_recipient;

@end
Forwarder.m
# import "Forwarder.h"
 
@implementation Forwarder
 
- (retval_t)forward:(SEL)sel args:(arglist_t) args {
 /*
  * Comprueba si el receptor responde al mensaje.
  * Esto puede ser o no deseable, por ejemplo, si un receptor
  * a su vez no responde el mensaje, podría reenviarlo él mismo.
  */
 if([recipient respondsToSelector:sel]) {
  return [recipient performv:sel args:args];
 } else {
  return [self error:"El receptor no responde"];
 }
}
 
- (id)setRecipient:(id)_recipient {
 [recipient autorelease];
 recipient = [_recipient retain];
 return self;
}
 
- (id) recipient {
 return recipient;
}
@end
Recipient.h
# import <objc/Object.h>
 
// Un simlpe objeto receptor.
@interface Recipient : Object
- (id)hola;
@end
Recipient.m
# import "Recipient.h"
 
@implementation Recipient
 
- (id)hola {
 printf("El receptor dice hola!\n");
 
 return self;
}
 
@end
main.m
# import "Forwarder.h"
# import "Recipient.h"
 
int main(void) {
 Forwarder *forwarder = [Forwarder new];
 Recipient *recipient = [Recipient new];
 
 [forwarder setRecipient:recipient]; // Elige el receptor.
 /*
  * Véase que el reenviante no ersponda al saludo! Será reenviado.
  * Todos los métodos no reconocidos serán reenviados al receptor
  * (si el receptor los responde, como se dice en el Forwarder)
  */
 [forwarder hello];
 
 [recipient release];
 [forwarder release];
 
 return 0;
}

Notas

Cuando se compila con gcc, el compilador reporta:


$ gcc -x objective-c -Wno-import Forwarder.m Recipient.m main.m -lobjc

main.m: In function `main':

main.m:12: warning: `Forwarder' no responde a `hola'

$

El compilador reporta lo comentando antes, que Forwarder no responde a mensajes hola. En esta circunstancia, es seguro ignorar el aviso ya que el reenvío fue implementando. La ejecución del programa produce esta salida:


& ./a.out

El receptor dice hola!

Other Languages
العربية: سي-الكائنية
azərbaycanca: Objective-C
български: Objective-C
català: Objective-C
čeština: Objective-C
Deutsch: Objective-C
Ελληνικά: Objective-C
English: Objective-C
Esperanto: Objective-C
français: Objective-C
galego: Objective-C
עברית: Objective-C
magyar: Objective-C
հայերեն: Objective-C
italiano: Objective-C
日本語: Objective-C
ქართული: Objective-C
қазақша: Objective-C
Кыргызча: Objective-C
Nederlands: Objective-C
polski: Objective-C
português: Objective-C
русский: Objective-C
Simple English: Objective-C
slovenčina: Objective-C
slovenščina: Objective-C
српски / srpski: Objective-C
svenska: Objective-C
тоҷикӣ: Objective-C
Türkçe: Objective-C
українська: Objective-C
中文: Objective-C
Bân-lâm-gú: Objective-C
粵語: Objective-C