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.

Anuncios

Aplicaciones Web como “verdaderas aplicaciones”. ¿Alguien dijo Chrome OS?

noviembre 13, 2009

wave header

Hoy en día todos usamos numerosas apliaciones web, en ciertos casos incluso han desplazado a las aplicaciones tradicionales.

Quizás la primera aplicación web que nos viene a la mente es GMail, en muchos ocasiones es el unico cliente de correo que utilizamos.

La forma de acceder a estas aplicaciones es mediante el navegador, sin duda una de sus grandes ventajas es el hecho de que pueden accederse desde cualquier lado sin embargo, el navegador es un “concepto” que no acaba de encajar con “aplicación”. Conceptualmente, el navegador no se asocia al hecho de alojar aplicaciones.

Cuando estamos manejando una aplicacion como Gmail o Google Wave, hay varias cosas de un navegador que no acaban de encajar, por ejemplo,

  • No necesitamos la barra de direcciones, no vamos a cambiar de página en el contexto de la aplicación
  • No necesitamos los controles del navegador, como por ejemplo para navegar por la historia, ya que dentro de una aplicación, no se vuelve hacia atras usando dichos controles.
  • No necesitamos las extensiones habituales del navegador, y de hecho prescindir de esto aligerará la memoria ocupada por la aplicación, que en el caso de ciertas aplicaciones ya es bastante.
  • En el caso de que la aplicación quiera enviar alguna notificación, como por ejemplo un mensaje nuevo recibido en GMail, será el navegador el encargado de enviar dicha notificación. Si tenemos en cuenta que una sesion actual en un navegador tiene muchas pestañas, la posibilidad de notificar directamente por parte de la aplicación web se complica.
  • La aplicación no aparece al mismo nivel que el resto de aplicaciones del sistema. Como ejemplo pongamos que queremos cambiar del Editor de Textos al gestor de correo, antes deberiamos pasar primero por el propio navegador. Cosa que es bastante incomodo.

Como vemos, las aplicaciones web piden a gritos salir del navegador y convertirse en aplicaciones “de ley” en nuestro escritorio.

Existen diversas alternativas para “extraer” estas aplicaciones del navegador y convertirlas en una mas de nuestro sistema operativo.

Una alternativa es Prism. Prism es un proyecto de mozilla, que como es evidente usa el motor de renderizado del firefox para crear mostrar la página web. El resultado que obtenemos de usar prism es un programa como otro cualquiera y que al ejecutarlo accederemos a la aplicación quitando todas las decoraciones del navegador.

Si usamos mac, tenemos Fluid, cuyo objetivo es similar.

Y como no, la estrella de toda esta iniciativa de aplicaciones web, google tiene su propia alternativa para crear aplicaciones reales a partir de sus similares en web.

Usando Chrome, podemos separar las webs como aplicaciones mediante la opción de menu llamada: “Crear accesos directos a aplicaciones…”. Usando esta opción las aplicaciones tendrán su propia entidad en el escritorio, apareciendo como cualquier otra en la barra de tareas, o como acceso directo junto al resto de aplicaciones del sistema. Además, cuando lancemos las aplicaciones desde este entorno, se eliminaran los elementos que antes mencionábamos como accesorios.

Wave en Chrome

Seguramente y como mencionaba en el titulo de la entrada, muy pronto estaremos hablando de este concepto mucho mas en serio, puesto que, sacando la bola de cristal del cajón, viendo las últimos movimientos de google, mucho me temo que la idea sobre la que se basa Chrome OS, puede ir por estos derroteros.


Los peligros de devolver punteros

noviembre 3, 2009

 

Cuando escoges un lenguaje que deja en tus manos la gestión de memoria tienes que tener mucho cuidado con la manera en que gestionas los recursos que están a tu disposición. Si bien es cierto que puedes tener mucha flexibilidad a la hora de manejar la forma en que pasas los datos a tus funciones o métodos, esa flexibilidad se convierte en responsabilidad a la hora de liberar de forma correcta lo que en algún momento reclamas.
Por norma, siempre es buena idea que algún punto de nuestro programa se haga responsable de la memoria que el mismo pide, de esta manera sabrá como y cuando liberarlo. Por ejemplo, si una función necesita pasar un espacio de memoria a otra función para que escriba en ella, la mejor idea es que la función cree el espacio y se lo pase a la función de lectura ya creado, de tal maneras que cuando lo use, sea capaz de eliminarlo sin problemas.
void f1()
{
  ...
  char* buffer = new char[100];
  f2(buffer);
  ...
  delete [] buffer;
  ...
}
Uno de los diseños que pueden inducir a problemas es el hecho de que una función devuelva un puntero a una memoria de la que dicha función pierde la responsabilidad. Por ejemplo
char* f2()
{
  char* buffer = new char[100];
  ...
  return buffer;
}
Este diseño, puede ocasionar que la memoria de buffer nunca sea liberada, por ejemplo, si usamos la llamada como parametro de otra función, esa memoria nunca se liberará, o bien por que directamente se nos olvide liberar la memoria que no hemos reservado en la función original, por ejemplo:
void f1()
{
  int len = strlen(f2()); // la memoria de f2 no es accesible
}
En este ejemplo vemos como la memoria que f2() ha reservado, nunca se libera, pues no hay ninguna variable que la referencie y que podamos usar para liberarla.