Capítulo I: Objective-C. Lección 5: Manejo de Memoria.

El manejo de memoria es muy importante, sobre todo en dispositivos portátiles como el iPhone y el iPod, que no cuentan con los recursos tan grandes que tienen las computadoras de escritorio de ahora. Este tema es por mucho uno de los más importantes, entre otras razones, si haces un buen manejo de memoria, evitarás bugs, leaks, y crashes, que son una verdadera molestia y algunos son difíciles de debuggear.

En las lecciones anteriores ya he realizado algunas actividades del manejo de memoria, pero es un tema tan importante que merece su propia lección.

Retain y Release.

El contador de retain (retain count) se utiliza para llevar un registro de las referencias activas hacia un mismo objeto. Todos los objetos tienen internamete este contador, de tal manera que si el contador de retain es mayor a cero, significa que aún esta siendo utilizado en alguna parte del código, pero una vez que llega a cero, se debe liberar la memoria que estaba usando.

Cuando se quiere agregar una referencia al objeto se llama al método retain: [objeto retain]; esto aumenta el contador de retain  en 1 y cuando deseamos remover una referencia llamaremos al método release: [objeto release]; esto disminuye el contador de retain en 1. Una vez que el contador llega a 0 (cero), se libera la memoria automáticamente con una llamada a [self dealloc]. Los métodos retain y release estan definidos en NSObject.

… -(void) dealloc {
//este método es llamado automáticamente cuando retain=0
//no debe ser llamado manualmente
[super dealloc]; } …

Algunas reglas generales son:

  • Debes liberar con release todo lo que tu creaste, todo lo que es creado con alloc, o new, o los métodos que contienen la palabra copy (newObject, copyWithZone, mutableCopy,…), también debes darle release, si manualmente diste un retain.
  • En otras palabras debe haber un (ni mas ni menos) release por cada retain, alloc, new o copy.
  • Para liberar la memoria puedes usar tanto release, como autorelease, la diferencia es que release, disminuye el contador de retain inmediatamente, mientras que autorelease lo hará en un momento en el futuro.
  • Un objeto que es recibido como retorno de algún método no debe ser liberado en el método receptor, por lo general esta garantizado que sea válido mientras dure el método que lo recibe e incluso puede ser devuelto al método que lo llamó. Aunque existen algunas excepciones como algunas aplicaciones multihilo, donde una combinación de retain y release o autorelease asegurarán que el objeto se mantenga válido mientras sea necesario.
  • Si deseas conservar un objeto en una variable de instancia debes darle retain o copy, generalmente se hace a través de accesadores. (ver más abajo).
#import #import int main( int argc, const char *argv[] ) {
NSString *uno =[[NSString alloc] initWithCString: “Uno”];
//al momento de crearlos tienen retain count=1
NSString *dos =[[NSString alloc] initWithCString: “Dos”]; //retain=1
NSString *tres =[[NSString alloc] initWithCString: “Tres”]; //retain=1
printf( “Retain count uno: %d\n”, [uno retainCount] ); //1
printf( “Retain count dos: %d\n”, [uno retainCount] ); //1
printf( “Retain count tres: %d\n”, [tres retainCount] ); //1
//Si agregamos las cadenas a un array, el array agregara 1 al retain count para asegurarse
//que los objetos sigan siendo válidos, serán liberados automáticamente cuando array sea liberado
NSArray *array=[NSArray arrayWithObjects: uno, dos, nil]; //uno++;dos++;
[dos release];
//dos– si libero la memoria de dos, el objeto sigue siendo válido ya que fué asegurado por array
printf( “Retain count uno: %d\n”, [uno retainCount] );
//2
printf( “Retain count dos: %d\n”, [uno retainCount] );
//1
printf( “Retain count tres: %d\n”, [tres retainCount] );
//sigue siendo 1 ya que no fue agregado al array     [uno release];
//uno–     [tres release];
//tres–
//dos no debe ser liberado otra vez, ya fue liberado una vez.
//en este punto uno y dos siguen siendo válidos, ya que array sigue siendo válido     printf( “Retain count uno: %d\n”, [uno retainCount] );
//1
printf( “Retain count dos: %d\n”, [uno retainCount] );
//1
//printf( “Retain count tres: %d\n”, [tres retainCount] );
//esta instancia ya no es válida     return 0; }

¿Cuándo hacer y cuando no hacer release?

Se debe llamar a release siempre cuando se tenga responsabilidad del objeto y ya no se necesite la referencia: Ej:
Correcto:

-(void) llamadoCorrectoDeRelease {     NSArray *arregloUno=[[NSArray alloc] init]; //como se llamó a alloc, es reponsabilidad de este método liberarlo en su momento     NSArray *arregloDos=[NSArray array]; //devuelve un arreglo que NO es responsabilidad de este método     NSArray *arregloTres=[arregloDos copy]; //devuelve una copia (no profunda) del objeto en arregloDos, responsabilidad del método     //Uso de los objetos….     //Liberacion     [arregloUno release]; //responsable por alloc     //[arregloDos release] // NO se debe llamar a release, no es responsabilidad del método, y si se libera causará una excepción     [arregloTres release]; //responsable por copy }

¿Cuándo hacer y cuando no hacer autorelease?

Se debe llamar a autorelease cuando se tenga responsabilidad del objeto y pero se necesite la referencia en un nivel superior de la pila de llamadas: Ej:
Correcto:

-(NSArray) llamadoCorrectoDeAutoRelease {     NSArray *arregloUno=[[NSArray alloc] init]; //como se llamó a alloc, es reponsabilidad de este método liberarlo en su momento     NSArray *arregloDos=[[NSArray alloc] init]; //como se llamó a alloc, es reponsabilidad de este método liberarlo en su momento     //Uso del array….     //liberación     [arregloDos release]; //después de este punto arregloDos ya no es referencia válida     //Se debe liberar ya que fué creado con alloc, pero aún se quiere usar en metodoInvocador, se usa autorelease y se devuelve al invocador     return [arregloUno autorelease]; //responsable por alloc } -(void) metodoInvocador {     NSArray *objetoAutoLiberado=[self llamadoCorrectoDeAutoRelease];     //se puede usar objetoAutoLiberado con seguridad y no debe ser liberado aquí, ya que es responsabilidad de llamadoCorrectoDeAutoRelease     //[objetoAutoLiberado release]; //llamar a release aquí es INCORRECTO!, ya se ha llamado a autorelease, no es necesiario hacer más }

Al llamar a autorelease, se te garantiza que la referencia será válida incluso si la devuelves al método invocador,

Propiedades, accesadores (o accesores?) y notación de punto.

Las propiedades y accesores (o accesadores, como sea, jeje), tienen algunas particularidades en objective-c y son de mis características favoritas, ya que ayudan al encapsulamiento y le dan un estilo bastante elegante.

¿Que son accesadores (accessors)? Son métodos para leer y escribir propiedades (variables de instancia) de un objeto. El método para escribir, se le llama Setter, y al método para leer se llama Getter, y son definidos automáticamente por objective-c:

//Persona.h @interface Persona : NSObject {     NSString *nombre; } @property (nonatomic, retain) NSString *nombre; -(void) usoDeAccesador; @end
//Persona.m #import “Persona.h” @implementation Persona @synthetize nombre; -(void) usoDeAccesador {     //Setter     [self setNombre:@”Pedro”];     //Getter     NSLog(@”%@”, [self getNombre]);     //Método abreviado con “.” (punto)     self.nombre=@”Yo no me llamo Javier”; //setter     NSLog(@”%@”, self.nombre); //getter } -(void) dealloc {     self.nombre=nil;     // también es posible usar lo siguiente, pero uno solo de los dos!     //[self release];     //creo que es mejor usar la opción primera (self.nombre=nil), porque se asegura que no envíes mensajes de manera accidental a una instancia inválida } @end
  • A través del accesador podemos obtener acceso a las variables de instancia, tanto para escribir como para leer.
  • Para definir los accesadores debemos declararlo primero con: @property (modificadores) TipoDeLaVariable *nombreDeVariable;
  • @synthetize se usa para que objective-c implemente automáticamente los métodos.

Existen diferentes modificadores para las propiedades:

  1. readonly: muy claro, indica que solo se creará el método para leer, y no para escribir.
  2. atomic / nonatomic: atomic indica que se implementará una cerradura para impedir que múltiples métodos (en programas multihilo) accedan a la propiedad, nonatomic no implementa la cerradura.
  3. assign: Se implementa el método de setter de tal manera que solo se asigna: seria equivalente a algo como: -(void) setPropiedad: (id)valor { propiedad=valor; } es decir una asignación simple.
  4. copy: Si se especifica copy, el método setter, se implementará haciendo una copia del objeto asignado, al hacer esto se debe liberar con release ya que fué copiado (copy)
  5. retain: Al igual que con copy, retain aumenta el contador de retain en 1, por lo que debe ser liberado con release cuando sea adecuado. ¿Cuándo es adecuado?, sigue leyendo. La implementación que objective-c hace automáticamente, sería parecida a la siguiente:
    -(void) setPropiedad: (id)valor {
    if (propiedad=nil) { [propiedad release]; }
    propiedad=[valor retain];
    }.
  6. Si se asigna un nuevo objeto a una propiedad marcada con retain, se libera cuando se asigna el nuevo objeto, pero, debes entender que no ocurre automáticamente sino cuando le asignas un nuevo objeto, por lo que el viejo es liberado, pero el nuevo no será liberado hasta que tu lo indiques. Lee a continuación:

Si utilizo copy o retain en la propiedad, ¿cuándo debo llamar a release? Dealloc!!

Depende de cada programa, pero sigue las mismas reglas, se libera cuando ya no se necesite, generalmente se libera cuando el objeto contenedor no es necesario, para esto utilizaremos el método especial dealloc.

El método dealloc es invocado automáticamente cuando el objeto va a morir, entonces ahí podemos liberar las propiedades que fueron marcadas con retain y copy.