NSZone, gestión de zonas de memoria en OSX

abril 21, 2010

Es posible que aunque desarrolleis aplicaciones en Objetive-C nunca os habeis visto en la necesidad de usar las zonas de memoria. Normalmente estos mecanismos se hacen de forma transparente y no tenemos que preocuparnos por ello. Pero si sois curiosos os llamará la atención como se asigna la memoria en las tripas del runtime del Objetive-C.

Al igual que mencionamos en el otro post de gestión de memoria en OSX, estos mecanismos, gracias al uso del garbage collector estan pasando a ser cosa del pasado. Ya que si usamos zonas con el recolector de basura activado, estas seran ignoradas. Sin embargo, y como dijimos en esa entrada, no siempre tenemos la posibilidad de desarrollar con el recolector de basura activado, como por ejemplo, si queremos que nuestra aplicación sea compatible con versiones anteriores de OSX o si desarrollamos para plataformas como iPhone o iPad donde esta funcionalidad aún no esta dispobible

Como se puede deducir del nombre del post, un NSZone representa una zona de memoria, que utilizaremos para alojar nuestros datos. Cuando creamos un programa, se crea una Zona por defecto, donde se alojaran todos nuestros objetos si no especificamos nada más, normalmente nos interesará crear y gestionar nuestra propia zona cuando vamos a hacer un uso especifico de dicha zona de memoria. Por ejemplo, si vamos a crear y liberar muchos objetos, quizas nos interese gestionar nuestra propia zona de memoria, en vez de realizar estas operaciones en la zona por defecto. Al alojar y desalojar memoria, siempre quedan zonas entre los diferentes objetos alojados que no son sencillas de reaprovechar, lo que aumenta la fragmentación, y teoricamente, empeora el rendimiento de nuestra aplicación. Además, y en teoria, los objetos de una zona estan próximos físicamente, con lo que si guardamos objetos relacionados entre si en la misma zona, el acceso desde uno a otro será mas rápido que si estan dispersos por la zona común.

Si teneis experiencia en C++, seguramente este concepto os recuerde a los “allocators” que se pueden utilizar cuando gestionamos ciertos objetos de STL.

Otro beneficio de la gestión manual de zonas es que si, colocamos N objetos en la memoria, será mucho mas rápido desalojarlos de nuestra propia zona que de la zona común del programa.

Hablando ya de ObjetiveC, la documentación es algo escasa, NSZone es una estructura de C, que guarda los datos referentes a la zona de memoria.

La función básica que nos sirve para crear una zona es:

NSZone * NSCreateZone (
NSUInteger startSize,
NSUInteger granularity,
BOOL canFree
);

A esta función le pasamos el tamaño inicial de la zona, el tamaño con el que crecerá o se encojerá en el parámetro granularity y le diremos si queremos que se libere memoria cuando se alojen nuevos objetos. Si le decimos que no, la reserva de memoria sera mas rápida ya que el algoritmo no tendrá que optimizar la zona cuando estemos reservando nueva memoria.

Para liberar una zona utilizaremos:

void NSRecycleZone (
NSZone *zone
);

Que no necesita mucha explicación, simplemente libera la zona que le pasamos como parámetro. La parte interesante, es que para prevenir problemas serios con objetos que esten en la zona cuando se Recicle, esta función, mueve los objetos vivos, de esta zona a la zona común.

Si queremos reservar memoria, explicitamente en una zona, podemos usar

void * NSZoneMalloc (
NSZone *zone,
NSUInteger size
);

Que nos reservara “size” bytes en la zona que le indicamos, devolviendonos un puntero a dicha porción de memoria.

Como podiamos esperar, la función que libera la memoria que acabamos de reservar es:

void NSZoneFree (
NSZone *zone,
void *ptr
);

Que es equivalente a lo que conocemos de “free” en C.

Todas estas funciones estan definidas en NSZone.h, dentro del Foundation framework

La gracia de las zonas, no es utilizar la memoria de esta forma “rudimentaria”, si no que, dicho concepto esta integrado completamente en el mecanismo de alocación de objetos en Objetive-C.

La clase padre de todas, NSObject, tiene dos metodos para alojar memoria, “alloc” y “allocWithZone:”.

De esta manera, cuando queremos crear un nuevo objeto, podemos usar esta variación de alloc, por ejemplo:

NSString * cadenaEnZona = [[NSString allocWithZone:mizona] init];

Que creará una cadena en la zona que le especificamos en lugar de en la zona común.

Debido a que este método esta en NSObject, cualquier objeto que creemos en nuestro proyecto, heredará este funcionamiento, y podremos instanciarlo en nuestra zona.

Además de este método allocWithZone, el otro método clave de NSObject para trabajar con Zones, es “copyWithZone:” que copiara el objeto especificado en otro, pero en la zona que le especificamos. Al igual que el método “copy”, el objeto a ser copiado, debe implementar la interfaz NSCopying, para asegurar que la copia del objeto se hace de forma adecuada.


Principios básicos de la gestión de memoria en Objective-C / OSX

noviembre 18, 2009
Gestión de memoria en Objective-C / OSX
La gestión de memoria siempre ha sido un caballo de batalla que los programadores de lenguajes de medio-bajo nivel tenemos que “sufrir”, si bien es cierto que la posibilidad de manejar manualmente la memoria es algo que da flexibilidad a la hora de optimizar un programa, en otros casos suele ser un dolor de cabeza.
Pese a que las nuevas versiones de OSX y Objetive-C han introducido mecanismos de recoleción de basura, se puede decir que Objetive-C es uno de esos lenguajes que nos deja controlar la memoria que ocupamos.
Dado a los avances actuales en las plataformas de programación, uno se puede preguntar para que es necesario controlar manualmente la reserva de memoria. Pese a que los algoritmos que se encargan de “reclamar” la memoria no ocupada cada vez son mejores, no deja de ser un proceso de fondo que se ejecuta con baja prioridad y que recolecta la memoria, dependemos de una tercera parte que vele por nuestra memoria.
Concretamente en el caso de Objective-C, si usamos el recolector de basura, hacemos que nuestro programa sea incompatible con versiones inferiores a la 10.5 (Leopard) que es donde se incluyo por primera vez este módulo. Además si nuestro objetivo es una plataforma como el iPhone donde los recursos son limitados, tampoco contamos con el recolector de basura. A parte de todo esto, desde el punto de vista academico siempre es bueno saber como funcionan las cosas antes de usar los metodos automaticos 😉
Una de las claves para la gestión de memoria eficiente es la liberación de la memoria que ya no se utiliza, para ello Objetive-C utiliza un método bastante usado en otros entornos, que es la cuenta de referencias, cuando se crea un objeto en memoria, la cuenta de referencias de dicho objeto se establece en 1, cuando ese objeto se deja de usar se resta esa cuenta, y cuando llega a 0 el objeto se elimina de memoria.
En codigo se utilizan tres mensajes son clave para una primera aproximación, “alloc”, “retain” y “release”.
Cuando construimos un objeto con init, incializamos el contador de referencias, si queremos incrementar el contador de un objeto, le enviariamos en mensaje retain, y cuando queremos decrementar ese contador usariamos release.
Por ejemplo
NSString* cad = [NSString initWithString:@”Probando memoria”]; // Contador de referencias a 1
NSLog(@”%@”, cad);
[cad release]; // Contador de referencias a 0, se elimina el objeto
NSLog(@”%@”, cad); // Error de acceso a memoria, cad ya no existe
Como vemos, el uso es bastante sencillo, veamos ahora un ejemplo en el cual deberiamos usar retain.

La gestión de memoria siempre ha sido un caballo de batalla que los programadores de lenguajes de medio-bajo nivel tenemos que “sufrir”, si bien es cierto que la posibilidad de manejar manualmente la memoria es algo que da flexibilidad a la hora de optimizar un programa, en otros casos suele ser un dolor de cabeza.

Pese a que las nuevas versiones de OSX y Objetive-C han introducido mecanismos de recoleción de basura, se puede decir que Objetive-C es uno de esos lenguajes que nos deja controlar la memoria que ocupamos.

Dado a los avances actuales en las plataformas de programación, uno se puede preguntar para que es necesario controlar manualmente la reserva de memoria. Pese a que los algoritmos que se encargan de “reclamar” la memoria no usada cada vez son mejores, no deja de ser un proceso de fondo que se ejecuta con baja prioridad y que recolecta la memoria, dependemos de una tercera parte que vele por nuestra memoria.

Concretamente en el caso de Objective-C, si usamos el recolector de basura, hacemos que nuestro programa sea incompatible con versiones inferiores a la 10.5 (Leopard) que es donde se incluyo por primera vez este módulo. Además si nuestro objetivo es una plataforma como el iPhone donde los recursos son limitados, tampoco contamos con el recolector de basura. A parte de todo esto, desde el punto de vista academico siempre es bueno saber como funcionan las cosas antes de usar los metodos automaticos 😉

La principal tarea para una gestión de memoria eficiente es la liberación de la memoria que ya no se utiliza, para ello Objetive-C utiliza un método bastante usado en otros entornos, dicho método es la cuenta de referencias, cuando se crea un objeto en memoria, la cuenta de referencias de dicho objeto se establece en 1, cuando ese objeto se deja de usar se resta esa cuenta, y cuando llega a 0 el objeto se elimina de memoria.

Para comprender la forma en que se gestiona la memoria en OSX es clave conocer el concepto de propiedad de un objeto.

Como hablamos en el post de los peligros a la  hora de devolver punteros, es el propietario de un objeto quien se hace responsable a la hora de liberarlo de la memoria. El principio básico es: si un objeto te pertenece, liberalo cuando dejes de usarlo, si por otro lado, no te pertenece, no debes liberarlo.

Por tanto la primera pregunta es, ¿Cuando somos dueños de un objeto?, pues principalmente cuando nosotros lo creamos, y esto es cuando usamos una llamada a alloc, algun mensaje que comience por new o que contenga copy.

En código se utilizan tres mensajes que son clave para una primera aproximación, “alloc”, “retain” y “release”.

Cuando construimos un objeto con init, incializamos el contador de referencias, si queremos incrementar el contador de un objeto, le enviariamos en mensaje retain, y cuando queremos decrementar ese contador usariamos release.

Por ejemplo

NSString* cad = [[NSString alloc] initWithString:@"Probando memoria"]]; 
  // Contador de referencias a 1
NSLog(@"%@", cad);
[cad release]; 
  // Contador de referencias a 0, se marca para eliminar el objeto
NSLog(@"%@", cad); 
  // cad ya no se asegura que apunte a un bloque de datos válidos.

Como hemos dicho, el objeto cad nos pertenece ya que lo hemos creado con alloc, asi que lo correcto es enviar al objeto el mensaje de release cuando ya no vamos a seguir usandolo.

Seguramente ya sabemos que hay otros métodos para obtener referencias a un objeto, por ejemplo:

NSString *cad = [NSString stringWithString:@"Hola"];
[cad release] // Error, el objeto no es nuestro

En este caso, el objeto no nos pertenece, por lo que no deberiamos liberarlo cuando dejemos de usarlo.

Para explicar este funcionamiento, es necesario conocer lo que se llama Autorelease Pool. En el caso anterior, en objeto no nos pertenece, pero se tiene que liberar de alguna manera, ahi es donde entra el autorelease pool, dicha funcionalidad se encargará de llamar al mensaje release de todos los objetos que estan en dicho pool cuando estos salgan fuera de ambito, o cuando le enviemos el mensaje drain al propio pool.

Supongamos el ejemplo:

- (NSString* ) getString ()
{
	NSString *result = [[NSString alloc] initWithString:@"prueba"]
	[result autorelease];
	return result;
}
...
NSString *cad = [objeto getString];

Ya que obtenemos el objeto cad, mediante un método que no transfiere la propiedad, el objeto se liberará cuando salga fuera de ambito. En el caso de Cocoa, se crea un pool justo antes de procesar un evento y se llama a drain justo cuando finaliza este evento.

Esta forma de gestionar el pool puede llegar a producir algun problema, ya que si queremos que un objeto que esta en el pool sobreviva a diferentes eventos, debemos enviarle el mensaje retain antes de que termine el evento, de otra manera, el objeto se destruirá.

Como hemos visto los conceptos básicos de la gestión de memoria en OSX son sencillos, pero es importante usarlos de forma correcta para evitar problemas cuando desarrollamos nuestra aplicacion para iPhone o en Cocoa.