Está en la página 1de 10

Objective-C para “lerdos” (que sepan Java o C#)

Primeros pasos:
Lo primero que debes saber es quién creó Objective-C, para saber quién es o son los culpables de tu
sufrimiento, y estos fueron Brad Cox y Tom Love a principio de los 80. Básicamente tomaron C y le
añadieron la programación orientada a objetos de una forma farragosa y fea.

Por ello lo primero que deberías saber es como programar en C, si además sabes C++, Java o C# te será más
sencillo porque esto no es una guía para enseñar los fundamentos de la programación orientada a objetos,
por lo que eso también deberías conocerlo antes de leerte esta guía básica.

Es recomendable también que mires por si quieres profundizar la documentación oficial de Apple sobre el
lenguaje y la gestión de la memoria, ya que por alguna razón esotérica resulta que en iPhone no hay
recolector de basura. Aquí tienes un par de enlaces con información.

 Introduction to The Objective-C Programming Language


 Memory Management Programming Guide for Cocoa

Creando y manejando clases:


Igual que pasa en C++, tendremos dos ficheros para crear nuestras clases, un .h para definirla y un .m para
implementarla. De esta forma tendremos que utilizar #import como señal para que el compilador sepa que
clases vamos a utilizar en nuestro código:

#import <Foundation/NSObject.h>
#import "Hamperdine.h"

Así que lo primero que hay que hacer es definir nuestra clase en el .h, para futuros usos:

@interface NombreDeLaClase : NombreDeLaClasePadre {


@private
// Declaración de los atributos privados
@protected
// Declaración de los atributos protegidos (opción por defecto)
@public
// Declaración de los atributos públicos
}
// Declaración de propiedades y métodos
@end

Cualquiera que haya programado en un lenguaje orientado a objetos típico como Java o C# verá ciertos
elementos familiares, pero así de primeras uno puede ver lo “extravagante” que resulta este lenguaje con
respecto a su sintaxis. Para declarar un atributo es tan simple como:

tipo nombre;
static tipo nombre;

Las propiedades las dejamos para más tarde de momento, así que vamos a ver como se declara un método
de la clase:
// Método estático de la clase
+ (tipo) nombre;
+ (tipo) nombre:(tipo)param1 … nombreArgN:(tipo)paramN;

// Método de la clase
- (tipo) nombre;
- (tipo) nombre:(tipo)param1 … nombreArgN:(tipo)paramN;

Los tipos que podemos usar son los mismos que en C, más algunos añadidos para Objective-C. Por ejemplo
está BOOL, que sirve para representar valores booleanos, siendo YES el equivalente a true y NO el de false.
Pero de todos los nuevos tipos id es relativamente importante, ya que representa de forma genérica
cualquier tipo de objeto. Realmente es lo mismo que poner NSObject *, que por defecto es la clase padre
de cualquier objeto en el lenguaje. Y ya que lo hemos mencionado sería recomendable conocer algunos
métodos principales de esta clase:

 - (id) init; → Es el método para inicializar el objeto, que a efectos prácticos funciona como si fuera
el constructor de la clase.
 - (NSString *) description; → Este método se utiliza como representación alfanumérica de la clase,
en C# su equivalente es el método ToString().
 - (BOOL) isEqual:(id)obj; → Sirve para comprobar si dos objetos son iguales en contenido, ya que
si no se sobre-escribe el método su implementación en NSObject tan solo se limita a comprobar si
el objeto actual y el pasado a la función tienen la misma dirección en la memoria. Esto solo nos
diría que son el mismo objeto, más que si se tratan de dos objetos distintos pero equivalentes en
contenido.

Una cosa curiosa sobre la sobre-escritura de métodos en las clases hijas, es que no hace falta declarar en la
definición de la clase que vamos a sobre-escribir un método del padre, simplemente se añade en la
implementación y asunto resuelto. No todo iba a ser malo en el lenguaje, ¿no? Así que pasemos a mirar la
implementación que iría en el .m:

#import "NombreDeLaClase.h"

@implementation NombreDeLaClase {
// Declaración de atributos
}
// Definición de propiedades y métodos
@end

Es parecido a la definición en el .h, pero para que termine de quedarnos claro pongamos un ejemplo de
clase. Primero la definición en el .h:

#import <Foundation/Foundation.h>

@interface Entity : NSObject {


NSString * name;

int x;
int y;
}
+ (Entity *) newEntityWithName:(NSString *)nameValue x:(int)xValue y:(int)yValue;
- (Entity *) initWithName:(NSString *)nameValue;
- (Entity *) initWithName:(NSString *)nameValue x:(int)xValue y:(int)yValue;
// Getters y setters
- (NSString *) name;
- (void) name:(NSString *)value;
- (int) x;
- (void) x:(int)value;
- (int) y;
- (void) y:(int)value;
@end

Y ahora la implementación de la clase en el .m, en el que aparecerán algunas cosas que luego se explicarán
con más detalle, por ahora tan solo es necesario quedarse con el patrón:

#import "Entity.h"

@implementation Entity

+ (Entity *) newEntityWithName:(NSString *)nameValue x:(int)xValue y:(int)yValue {


return [[Entity alloc] initWithName:nameValue x:xValue y:yValue];
}

- (id) init {
self = [super init];
if(self != nil) {
name = @"";
x = 0;
y = 0;
}
return self;
}

- (Entity *) initWithName:(NSString *)nameValue {


self = [super init];
if(self != nil) {
name = [nameValue retain];
x = 0;
y = 0;
}
return self;
}

- (Entity *) initWithName:(NSString *)nameValue x:(int)xValue y:(int)yValue {


self = [super init];
if(self != nil) {
name = [nameValue retain];
x = xValue;
y = yValue;
}
return self;
}
- (void) dealloc {
[name release];
}

- (NSString *) name {
return name;
}

- (void) name:(NSString *)value {


if(name != value) {
[name release];
name = [value retain];
}
}

- (int) x {
return x;
}

- (void) x:(int)value {
x = value;
}

- (int) y {
return y;
}

- (void) y:(int)value {
y = value;
}

@end

Vemos varias cosas familiares como super para invocar métodos de la clase padre y self, que no hace falta
ser muy intuitivo para imaginarse que hace la misma función que this (en C++, Java o C#), que es el puntero
o referencia a la propia clase. Luego están las llamadas retain y release que ya veremos para qué sirven en
el capítulo dedicado a la gestión de la memoria. Pero para entenderlo ya del todo veamos una aplicación de
ejemplo sencillita:

#import "Entity.h"

int main (int argc, const char * argv[]) {


NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

Entity * guy01 = [[Entity alloc] newEntityWithName:@"Peter"];


Entity * guy02 = [Entity newEntityWithName:@"John" x:10 y:20];

NSLog(@"%@ = (%d, %d)", [guy01 name], [guy01 x], [guy01 y]);


NSLog(@"%@ = (%d, %d)", [guy02 name], [guy02 x], [guy02 y]);

[guy01 release];
[guy02 release];
[pool release];
return 0;
}

Con este ejemplo vemos como crear instancias de objetos, pidiendo espacio en memoria para ellos con
alloc e inicializándolos con init o sus variantes dependiendo del objeto. También está en el ejemplo una
llamada a NSLog que funciona parecido a printf pero para poder trabajar con las cadenas nativas de
Objective-C que son las NSString y que para indicarlas como valores literales en el código tenemos que
empezar la cadena poniendo una @ delante. Para indicar en la cadena de formato que va un objeto hay
que usar %@ que hará que NSLog invoque el método description del objeto para formar la cadena que
mostrará finalmente en pantalla.

Otras curiosidades es que para indicar el valor nulo (null en Java y C#) se emplea nil. También existe una
función assert que se llama NSAssert para parar la ejecución si no se cumple alguna condición necesaria
mientras estamos depurando la aplicación.

La ruin gestión de la memoria:


Objective-C 2.0 introdujo un recolector de basura, porque seguramente la mayoría de la gente estaba harta
del sistema tradicional para gestionar los objetos de este lenguaje. Pero para desgracia del personal, resulta
que en iPhone no está soportada esta novedad de la versión 2.0, con lo que hay que hacerlo al modo
antiguo. La idea es parecida a la idea de los punteros inteligentes, donde cada instancia tiene un contador
de referencias y cuando este llega a cero, el objeto será liberado de la memoria. La idea es buena en el
fondo, para evitar ciertos problemas que ocurren en C++, pero no es que la vida se vuelva más sencilla
precisamente.

Cada vez que creamos un nuevo objeto con alloc, new, copy o mutableCopy, el contador de referencias se
pone a 1, a partir de entonces cada vez que lo referenciemos desde otro puntero, tendremos que llamar a
retain para aumentar el contador. De la misma forma, cada vez que vayamos a dejar de darle uso,
deberemos de invocar a release para disminuir el contador. También debemos tener en cuenta que por lo
general todas las colecciones de datos del API, como NSArray o NSMutableArray, al añadir un objeto en
ellas, se les aplica el método retain de forma automática, es decir que los objetos de la API oficial de Apple
a los que les pasemos objetos creados por nosotros, para almacenarlos en su interior los retendrán
aumentando el contador y los liberarán cuando este objeto sea destruido (llamando a release).

Por ello en los métodos de nuestras clases, por ejemplo en los setters tendremos que actualizar la cuenta
para evitar en algún momento dado intentar acceder a algo que ya no existe. Por ejemplo:

- (void) name:(NSString *)value {


if(name != value) {
[name release];
name = [value retain];
}
}

Aquí miramos primero que no estamos asignando la misma instancia que ya teníamos asignada. Si no es la
misma, liberamos la anterior y retenemos la nueva. Por suerte retain devuelve el propio objeto, por lo que
podremos asignar y retener en la misma línea sin problemas. Pero no todo es amor en la villa del señor,
porque si por ejemplo, como suele ocurrir con el método description, devolvemos una cadena y le
aplicamos el release antes de devolverla se habrá borrado sin que la hayamos podido retener desde el
método invocador. Pero si no hacemos nada, esa variable no se va a eliminar y muchas veces, como suele
ocurrir con las cadenas, ni siquiera las vayamos a asignar a un puntero nuestro, sino que lo que nos
devuelva el método se lo pasamos a otro y ya está. Al final tendríamos un memory leak serio. La solución a
esto consiste en hacer lo siguiente, aplicándolo a Entity por ejemplo:

- (NSString *) description {
NSString * result = [[NSString alloc] initWithFormat:@"%@ = (%d, %d)", name, x, y];
[result autorelease];
return result;
}

El método autorelease es utilizado para añadir al objeto invocador al último NSAutoreleasePool creado en
el momento de la llamada. Por decirlo de algún modo esta clase es un recolector de referencias de objetos
“perdidos”, que dispone de un método llamado drain (que es llamado también desde el método release),
que lo que hace es borrar todos los objetos que ha recolectado hasta ese momento. No es tan elegante
como un recolector de basura, pero es la única opción sencilla. Estos recolectores pueden ser anidados sin
que haya problemas, por lo que son una herramienta útil para asegurarnos de eliminar “basura” después
de algún algoritmo que sepamos que genera muchos objetos que se quedan en el limbo.

¿Y qué pasa si queremos retener un objeto que ha sido marcado como autorelease? Pues que simplemente
con llamar a retain se quita del recolector y pasamos a hacernos cargo de su vida nosotros. Así que todos
los métodos que devuelven un objeto nuevo (que no sean alloc, new, copy o mutableCopy), como
description, añaden con autorelease el objeto devuelto al recolector activo en ese momento.

Además de todo lo anterior, para que funcione este sistema tendremos que sobrecargar el método dealloc,
que a efectos prácticos es como el destructor de la clase, al llegar el contador de referencias a cero, se
llama a dealloc y luego se borra el objeto. En el ejemplo de Entity veíamos que tan solo teníamos un objeto,
name, por lo que ese era el único que teníamos que liberar.

Declarando y manejando propiedades:


En el ejemplo anterior hemos creado getters y setters para poder acceder a los atributos de la clase, pero
en la versión 2.0 del lenguaje se incorporó una forma menos dura y más natural, que consiste en una idea
similar a las propiedades de C#. Primero hay que definir la propiedad en la interfaz de la clase con:

@property tipo nombre;


@property (atributos) tipo nombre;

Como se puede ver, los atributos, son opcionales y ya veremos cuáles son los que actúan por defecto:

 Métodos para acceder:


o getter=nombre → Por defecto, si no se indica nada, el getter tendrá el mismo nombre que
el identificador de la propiedad.
o setter=nombre → Por defecto, si no se indica nada, el setter tendrá como nombre al
identificador de la propiedad precedido de la palabra set. Por ejemplo, si nuestra
propiedad se llama name, el setter por defecto sería setName.
 Escritura o solo lectura:
o readwrite → Es una de las opciones por defecto en la definición de las propiedades, por lo
que no hace falta ponerlo, y lo que permite es que se pueda leer y escribir su contenido.
o readonly → Esta opción indica que la propiedad es solo de lectura.
 Tipo de asignación:
o assign → Se realiza la asignación sin invocar a retain. Este tipo se suele utilizar para
propiedades de tipos que no son objetos como int. Por defecto una propiedad utiliza esta
opción, por lo que es algo que se suele indicar al definir las propiedades, ya que lo normal
es trabajar con objetos con bastante frecuencia.
o retain → Se realiza la asignación invocando a retain. Este tipo solo se puede utilizar para
propiedades de tipos que son objetos de Objective-C.
o copy → Se le asigna a la propiedad una copia del objeto pasado al setter.
 Atomicidad:
o atomic → Es la opción por defecto y lo que hace es bloquear la asignación para que ningún
hilo que esté ejecutándose al mismo tiempo, pero no haya sido el que ha invocado al setter,
no pueda modificar la propiedad mientras lo está haciendo otro hilo.
o nonatomic → En esta opción no hay bloqueo, por lo que cualquier hilo puede modificar el
valor sin tener que esperar a que termine el otro. Esto obviamente puede ser problemático,
pero en aplicaciones que solo tienen un hilo se utiliza esta opción para ahorrar operaciones
inútiles que no servirían de nada.

Definidas las propiedades en nuestra clase viene la implementación y para ello se utiliza:

@synthesize nombre;
@synthesize nombre = nombreAtributo;
@dynamic nombre;

Con @synthesize lo que le estamos indicando al compilador es que si el programador no ha creado los
métodos a mano, que los cree automáticamente. Y con @dynamic se le indica que el programador es el
que se va a encargar de crear los métodos para la propiedad, por lo que el compilador no hará nada y si no
aparecen dará error al compilar. Obviamente si se crean a mano han de cumplir las opciones indicadas en
la definición de la propiedad, para evitar problemas en la ejecución.

Existe la opción de sobre-escribir propiedades definidas en clases padres, protocolos (que son el
equivalente a las interfaces de Java o C#) o de las categorías, pero tan solo se puede cambiar opción sobre
si es de lectura/escritura o solo de lectura no puede ser alterada en la sobre-escritura, el resto tendrán que
ser iguales a la definición original.

Luego tenemos algunas peculiaridades sobre las propiedades. Por ejemplo al indicar la opción copy
tendremos problemas si el tipo de la propiedad es un objeto “mutable”, porque el método copy devuelve
objetos que no se pueden modificar después de inicializados. Por ello, el programador tendrá que crear a
mano el setter y usar mutableCopy como método para copiar el objeto pasado al método.

Otra peculiaridad “rara” tiene que ver con dealloc, ya que por extrañas circunstancias las propiedades de
objetos que son sintetizadas no te permiten invocar a release desde dealloc, que es la forma normal de
trabajar cuando no se usa propiedades. Lo que tienes que hacer es asignarle el valor nil a la propiedad de
alguna de las dos siguientes formas:

// Forma A
[self setNombre:nil];

// Forma B
self.nombre = nil;
Y todo esto es lo que hay que hacer para poder utilizar el operador punto, para acceder a los atributos de la
clase, en vez de utilizar un método get o set, y lograr que programar en este lenguaje parezca más “natural”.

Los protocolos:
Para los programadores de Java y C# las interfaces son un concepto bien conocido y útil, ya que en esos
lenguajes para evitar que la gente se fuera de madre se eliminó la opción de la herencia múltiple.
Objective-C tampoco se puede tener más de un padre, por lo que existe lo mismo, pero se le llama
protocolo porque así queda más cool o yo qué demonios sé. El caso es que se declaran así:

@protocol Nombre <NombreProtocoloPadre1, ... , NombreProtocoloPadreN>

// Declaración de métodos cuya implementación es obligatoria

@optional

// Declaración de métodos cuya implementación es opcional

@required

// Declaración de métodos cuya implementación es obligatoria

@end

Lo primero que uno puede ver es que los protocolos pueden heredar de uno o varios protocolos, que
también pasa en otros lenguajes utilizados hoy en día. Lo segundo y sorprendente es que existen métodos
que podemos indicar como opcionales su implementación. Y lo tercero es que la sección @required es
completamente “inútil”, ya que en la sección por defecto actúa de la misma forma. Esta es la forma formal
para especificar un protocolo, existe una informal que consiste en hacer una categoría (un concepto que se
verá de forma más extensa en el siguiente apartado de la guía) sobre NSObject, con lo que todo el mundo
tendría los métodos de este protocolo informal:

@interface NSObject (Nombre)

// Declaración de métodos

@end

El caso es que siguiendo con los protocolos formales, la forma de indicar que una clase va a implementar
uno en particular es la siguiente:

@interface NombreClase <NombreProtocolo1, ... , NombreProtocoloN > {


// Declaración de atributos
}

// Declaración de métodos y propiedades

@end

Pero para anticipar a los problemas existe un par de métodos interesantes para comprobar si podemos
trabajar tranquilamente con una clase, el primero es conformsToProtocol, que comprueba si el objeto que
lo invoca tiene implementados todos los métodos obligatorios de un protocolo. El segundo es
respondsToSelector, que comprueba si existe un método en el objeto que lo invoca. Que ya dicho sea de
paso, para Objective-C los selectores son algo así parecido a los punteros a funciones, que podemos
pasárselo a una función para que esta lo use invocándolo, pero como es un tema complicado lo mejor es
mirar la documentación oficial para comprender el tema mejor. De momento nos quedaremos con un
ejemplo de cómo se utilizan los dos métodos que acabamos de comentar:

BOOL result = [nombreObjeto conformsToProtocol:@protocol(NombreProtocolo)];

BOOL result = [nombreObjeto respondsToSelector:@selector(nombreMetodo)];

Al indicar el tipo de una variable podemos asignarle una instancia de esa clase o la de alguna clase hija en
su jerarquía. Con los protocolos también podemos hacer lo mismo, para limitar la clase de datos que se
pueden instanciar en una variable:

NombreClase <protocolos, ...> * nombreVariable;

id <protocolos, ...> * nombreVariable;

De la primera forma tendrá que cumplir los protocolos indicados y ser parte de la jerarquía de la clase
indicada. De la segunda forma puede ser cualquier clase, pero tendrá que cumplir los protocolos indicados.

Las categorías:
Puede ocurrir que uno desee extender la funcionalidad de una clase cualquiera y las categorías es la forma
de realizar esto (en otros lenguajes dinámicos existen cosas parecidas, al igual que en C#). La sintaxis para
definir e implementar una categoría es sencilla, detrás del nombre de la clase se pone entre paréntesis el
nombre de la categoría y asunto resuelto:

#import "Victima.h"

@interface Victima (Nombre)

// Definición de los miembros

@end

En el fichero de implementación .m iría un código parecido a este:

#import "Victima+Nombre.h"

@implementation Victima (Nombre)

// Implementación de los miembros

@end

Razones para extender clases existentes hay muchas y que cada cual encuentre la que más rabia le dé. Se
pueden sobre-escribir métodos de las clases superiores en la jerarquía, pero obviamente no se puede hacer
lo mismo con las de la clase u otras categorías anexionadas a esta clase. Además hay que tener cuidado de
qué métodos añadimos y a quién, porque podría provocar que implosione nuestro programa.

Enumeración rápida:
En las colecciones de datos existe lo que se llama un “enumerador” que sirve para recorrer la estructura,
obteniendo uno a uno los datos que contienen. Por suerte alguien se dio cuenta de que era un coñazo no
tener una forma más sencilla de especificar bucles para recorrer las estructuras, así que se creó en Java y
C# una alteración para la instrucción for. Por suerte en Apple a alguien también se le iluminó la cabeza y
añadieron la misma idea:
for(tipo variable in colección) { ... }

Esta instrucción se le puede aplicar a cualquier colección que implemente el protocolo NSFastEnumeration.
Para ver un ejemplo de cómo iría esto:

NSArray * numbers = [NSArray arrayWithObjects: @"ichi", @"ni", @"san", @"shi", nil];

for(NSString * number in numbers) {


NSLog(@"Número: %@", number);
}

También los enumeradores tienen implementado el protocolo para la enumeración rápida, por lo que
podemos utilizar alguno de ellos en alguna ocasión, como por ejemplo para recorrer una estructura al revés.

Manejando excepciones:
Todo el que ha programado en un lenguaje orientado a objetos conoce lo que son las excepciones y sino
aprended POO primero por dios. Así que pasaremos a la parte en la que vemos lo parecido que es a otros
lenguajes como Java o C#. La clase base para excepciones que trae el SDK de Apple se llama NSException y
en este ejemplo descubriremos como lanzar una:

NSException *exception = [NSException exceptionWithName:@"EscozorInfernal"


reason:@"Me escuece horrores programar en esta m***da" userInfo:nil];

@throw exception;

Ahora sabemos lanzarlas, pero hay que saber cómo capturarlas y la forma es idéntica a otros lenguajes
pero con el “estilo” particular de Objective-C:

@try {
// Código sospechoso de fallar como una escopeta de feria…
}
@catch (NSException * exception) {
// Código para manejar la excepción capturada…
}
@finally {
// Código que se ejecutará siempre, pase lo que pase…
}

Como se puede observar el esquema es idéntico al que se usa en Java o C#, así que nada nuevo bajo el cielo.
Si queremos capturar cualquier tipo de posible excepción, incluso las que no heredan de NSException,
podemos poner (id exception) en el bloque @catch, para capturar cualquier cosa.

Más allá se encuentra un mundo todavía peor:


Hay más cosas que no he comentado en esta guía que son “interesantes”, como la interacción de nuestras
clases controladoras con la interfaz creada desde el Interface Builder (los marcadores IBOutlet e IBAction),
el tema de los selectores, las colecciones de datos del SDK oficial, etcétera. Pero existen tutoriales y video-
tutoriales bastante buenos por internet para aprender a desarrollar aplicaciones con Xcode, Cocoa y
CocoaTouch. Tan solo espero que esta guía os pueda servir para conocer la base mínima con la que trabajar
en este “maravilloso” lenguaje.

Un saludo,
Gorka Suárez.

También podría gustarte