Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Objective C PDF
Objective C PDF
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.
#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:
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>
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
- (id) init {
self = [super init];
if(self != nil) {
name = @"";
x = 0;
y = 0;
}
return self;
}
- (NSString *) name {
return name;
}
- (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"
[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.
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:
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.
Como se puede ver, los atributos, son opcionales y ya veremos cuáles son los que actúan por defecto:
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í:
@optional
@required
@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:
// 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:
@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:
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:
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"
@end
#import "Victima+Nombre.h"
@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:
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:
@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.
Un saludo,
Gorka Suárez.