Objective-C Sucintamente Gestión de la memoria

La memoria debe asignarse para cada objeto que usa su aplicación, y debe ser desasignada cuando la aplicación se realiza con ella para garantizar que la aplicación esté usando la memoria de la manera más eficiente posible. Es importante comprender el entorno de administración de memoria de Objective-C para asegurarse de que su programa no pierda memoria o intente hacer referencia a objetos que ya no existen.

Contando referencias a un objeto

A diferencia de C #, Objective-C hace no utilizar la recolección de basura. En su lugar, utiliza un entorno de conteo de referencias que rastrea cuántos lugares están usando un objeto. Mientras haya al menos una referencia al objeto, el tiempo de ejecución de Objective-C se asegura de que el objeto resida en la memoria. Sin embargo, si ya no hay referencias al objeto, el tiempo de ejecución puede liberar el objeto y usar la memoria para otra cosa. Si intenta acceder a un objeto después de que se haya liberado, lo más probable es que su programa se bloquee..

Hay dos formas mutuamente excluyentes de administrar referencias de objetos en Objective-C:

  1. Enviar métodos manualmente para aumentar / disminuir el número de referencias a un objeto.
  2. Deje que el nuevo esquema de recuento automático de referencias (ARC) de Xcode 4.2 haga el trabajo por usted.

ARC es la forma preferida de administrar la memoria en nuevas aplicaciones, pero aún así es importante entender lo que sucede debajo del capó. La primera parte de este capítulo le muestra cómo rastrear manualmente las referencias de objetos, y luego hablaremos sobre las implicaciones prácticas de ARC.


Gestión de memoria manual

Para experimentar con cualquiera de los códigos en esta sección, deberá desactivar el conteo automático de referencias. Puede hacerlo haciendo clic en el HelloObjectiveC Proyecto en el panel de navegación de Xcode:

El proyecto HelloObjectiveC en el panel de navegación.

Esto abre una ventana que le permite ajustar la configuración de compilación para el proyecto. Discutiremos la configuración de compilación en la segunda mitad de esta serie. Por ahora, todo lo que necesitamos encontrar es la bandera ARC. En el campo de búsqueda en la esquina superior derecha, escriba recuento automático de referencias, y debería ver aparecer la siguiente configuración:

Deshabilitando el conteo automático de referencias

Haga clic en las flechas al lado de y cambiarlo a No para deshabilitar ARC para este proyecto. Esto le permitirá utilizar los métodos de administración de memoria descritos en los siguientes párrafos..

La administración de memoria manual (también llamada liberación manual o MMR) gira en torno al concepto de "propiedad" del objeto. Cuando creas un objeto, te dicen que propio el objeto: es tu responsabilidad liberar el objeto cuando hayas terminado con él. Esto tiene sentido, ya que no querrá que aparezca otro objeto y lo libere mientras lo está utilizando.

La propiedad del objeto se implementa a través del conteo de referencias. Cuando reclama la propiedad de un objeto, aumenta su conteo de referencia en uno, y cuando renuncia a la propiedad, disminuye su conteo de referencia en uno. De esta manera, es posible garantizar que un objeto nunca se liberará de la memoria mientras otro objeto lo esté utilizando. NSObject y el protocolo NSObject definen los cuatro métodos principales que admiten la propiedad de objetos:

  • +(id) alloc - Asigne memoria para una nueva instancia y reclame la propiedad de esa instancia. Esto aumenta la cuenta de referencia del objeto en uno. Devuelve un puntero al objeto asignado..
  • -(id) retener - Reclamar la propiedad de un objeto existente. Es posible que un objeto tenga más de un propietario. Esto también incrementa la cuenta de referencia del objeto. Devuelve un puntero al objeto existente..
  • -liberación (nula) - Renuncia a la propiedad de un objeto. Esto disminuye la cuenta de referencia del objeto..
  • -(id) autorelease - Renuncie a la propiedad de un objeto al final del bloque de agrupación de autorelease actual. Esto disminuye el recuento de referencia del objeto, pero le permite seguir usando el objeto aplazando la versión real hasta un momento posterior. Devuelve un puntero al objeto existente..

Para cada asignar o conservar Método al que llamas, necesitas llamar lanzamiento o autorelease en algún punto de la línea La cantidad de veces que reclamas un objeto. debe igual a la cantidad de veces que lo sueltas. Llamando un extra asignar/conservar resultará en una pérdida de memoria, y llamar a un extra lanzamiento/autorelease intentará acceder a un objeto que no existe, lo que provoca que el programa se bloquee.

Todas sus interacciones de objetos, independientemente de si los está utilizando en un método de instancia, captador / configurador o una función independiente, deben seguir el patrón de reclamación / uso / libre, como se muestra en la siguiente muestra:

Ejemplo de código incluido: Memoria manual

int main (int argc, const char * argv []) // Reclamar el objeto. Persona * franco = [[Asignación de persona] init]; // Usa el objeto. frank.name = @ "Frank"; NSLog (@ "% @", frank.name); // Libera el objeto. [lanzamiento franco]; devuelve 0; 

los [Persona asignada] conjuntos de llamadas francola cuenta de referencia a uno, y [lanzamiento franco] lo decrementa a cero, permitiendo que el tiempo de ejecución lo elimine. Tenga en cuenta que tratando de llamar a otro [lanzamiento franco] resultaría en un choque, ya que la franco la variable ya no existe en la memoria.

Cuando se utilizan objetos como una variable local en una función (por ejemplo, el ejemplo anterior), la administración de la memoria es bastante sencilla: simplemente llame lanzamiento Al final de la función. Sin embargo, las cosas pueden complicarse al asignar propiedades dentro de los métodos de establecimiento. Por ejemplo, considere la siguiente interfaz para una nueva clase llamada Enviar:

Ejemplo de código incluido: Memoria manual - Referencia débil

// Ship.h #import "Person.h" @interface Ship: NSObject - (Person *) captain; - (void) setCaptain: (Person *) theCaptain; @fin

Esta es una clase muy simple con métodos de acceso definidos manualmente para un capitán propiedad. Desde la perspectiva de la gestión de la memoria, hay varias formas en que se puede implementar el configurador. Primero, tome el caso más simple donde el nuevo valor simplemente se asigna a una variable de instancia:

// Ship.m #import "Ship.h" @implementation Ship Person * _captain;  - (Persona *) capitán return _captain;  - (void) setCaptain: (Person *) theCaptain _captain = theCaptain;  @final

Esto crea una referencia débil porque el Enviar instancia no toma posesión de la el capitán Objeto cuando se asigna. Si bien no hay nada de malo en esto y su código seguirá funcionando, es importante comprender las implicaciones de las referencias débiles. Considere el siguiente fragmento de código:

#importar  #import "Person.h" #import "Ship.h" int main (int argc, const char * argv []) @autoreleasepool Person * frank = [[alloc alloc] init]; Ship * discoveryOne = [[Ship alloc] init]; frank.name = @ "Frank"; [discoveryOne setCaptain: frank]; NSLog (@ "% @", [discoveryOne captain] .name); [lanzamiento franco]; // [discoveryOne captain] ahora no es válido. NSLog (@ "% @", [discoveryOne captain]. Name); [discoveryOne release];  devuelve 0; 

Vocación [lanzamiento franco] decrementos francoel conteo de referencia a cero, lo que significa que el tiempo de ejecución puede desasignarlo. Esto significa que [descubrimiento, un capitán] ahora apunta a una dirección de memoria no válida, aunque descubrimiento uno nunca lo solté.

En el código de ejemplo proporcionado, observará que hemos agregado un Dealloc anulación del método en la clase Persona. Dealloc se llama cuando la memoria está a punto de ser liberada. Normalmente deberíamos manejar Dealloc y libere cualquier referencia de objeto anidado que tengamos. En este caso, liberaremos la propiedad de nombre anidado que tenemos. Tendremos más que decir sobre Dealloc en el siguiente capitulo.

Si intentara acceder a la propiedad, lo más probable es que su programa se bloquee. Como puede ver, debe ser muy cuidadoso al rastrear las referencias de objetos cuando usa propiedades con referencias débiles.

Referencia débil al valor del capitán.

Para relaciones de objeto más robustas, puede usar referencias fuertes. Estos se crean reclamando el objeto con un conservar llamar cuando se le asigna:

Ejemplo de código incluido: Memoria manual - referencia fuerte

- (void) setCaptain: (Person *) theCaptain [_captain autorelease]; _captain = [theCaptain retain]; 

Con una referencia fuerte, no importa lo que otros objetos estén haciendo con el el capitán objeto, ya que conservar asegura que se mantendrá alrededor mientras Enviar la instancia lo necesita. Por supuesto, necesitas equilibrar la conservar llame liberando el valor anterior; si no lo hiciera, su programa perdería memoria cada vez que alguien asignara un nuevo valor al capitán propiedad.

Fuerte referencia al valor del capitán.

Objetos de auto-liberación

los autorelease el método funciona de manera parecida lanzamiento, excepto que la cuenta de referencia del objeto no se decrementa inmediatamente. En cambio, el tiempo de ejecución espera hasta el final de la corriente @autoreleasepool Bloquear para llamar a un normal lanzamiento en el objeto. Es por esto que la main.m plantilla siempre está envuelta en una @autoreleasepool-se asegura de que todos los objetos en cola con autorelease las llamadas son actualmente Publicado al final del programa:

int main (int argc, const char * argv []) @autoreleasepool // Inserte el código para crear y autorelease los objetos aquí. NSLog (@ "¡Hola, mundo!"); // Todos los objetos lanzados automáticamente son * en realidad * liberados aquí.  devuelve 0; 

La idea detrás de la liberación automática es otorgar al propietario de un objeto la capacidad de renunciar a la propiedad sin destruir el objeto. Esta es una herramienta necesaria en situaciones en las que necesita devolver un nuevo objeto desde un método de fábrica. Por ejemplo, considere el siguiente método de clase definido en Nave.m:

+ (Ship *) shipWithCaptain: (Person *) theCaptian Ship * theShip = [[Ship alloc] init]; [theShip setCaptain: theCaptian]; devuelve la nave; 

Este método crea, configura y devuelve un nuevo Enviar ejemplo. Pero hay un problema serio con esta implementación: da como resultado una pérdida de memoria. El método nunca renuncia a la propiedad del objeto, y los llamadores de shipWithCaptain no sabe que necesitan liberar el objeto devuelto (ni deberían tener que hacerlo). Como resultado, la el barco objeto nunca será liberado de la memoria. Esta es precisamente la situación. autorelease fue diseñado para. La implementación adecuada se muestra aquí:

+ (Ship *) shipWithCaptain: (Person *) theCaptian Ship * theShip = [[Ship alloc] init]; [theShip setCaptain: theCaptian]; return [theShip autorelease]; // ¡Debe ceder la propiedad! 

Utilizando autorelease en lugar de un inmediato lanzamiento permite que la persona que llama use el objeto devuelto mientras sigue renunciando a su propiedad en la ubicación correcta. Si recuerda el capítulo Tipos de datos, creamos todas nuestras estructuras de datos de la Fundación utilizando métodos de fábrica de nivel de clase. Por ejemplo:

NSSet * crew = [NSSet setWithObjects: @ "Dave", @ "Heywood", @ "Frank", @ "HAL", nil];

los setWithObjects método funciona exactamente como el shipWithCaptain Método descrito en el ejemplo anterior. Devuelve un objeto publicado automáticamente para que la persona que llama pueda usar el objeto sin preocuparse por la administración de la memoria. Tenga en cuenta que hay métodos de instancia equivalentes para inicializar objetos de Foundation. Por ejemplo, el personal El objeto en la última muestra se puede crear manualmente de la siguiente manera:

// Crear y reclamar el conjunto. NSSet * crew = [[NSSet alloc] initWithObjects: @ "Dave", @ "Heywood", @ "Frank", @ "HAL", nil]; // Usa el set ... // Libera el set. [liberación de la tripulación];

Sin embargo, utilizando métodos de clase como setWithObjects, arrayWithCapacity, etc., es generalmente preferido sobre el asignar/en eso.

Atributos de liberación / liberación manual

Tratar con la memoria detrás de las propiedades de un objeto puede ser una tarea tediosa y repetitiva. Para simplificar el proceso, Objective-C incluye varios atributos de propiedad para automatizar las llamadas de administración de memoria en las funciones de acceso. Los atributos descritos en la siguiente lista definen el comportamiento del configurador en manual Entornos de recuento de referencias. Hacer no tratar de usar asignar y conservar en un entorno de conteo automático de referencias.

  • asignar - Almacenar un puntero directo al nuevo valor sin ningún conservar / lanzamiento llamadas Este es el equivalente automatizado de una referencia débil..
  • conservar - Almacenar un puntero directo al nuevo valor, pero llamar lanzamiento en el valor anterior y conservar en el nuevo. Este es el equivalente automatizado de una referencia fuerte..
  • dupdo - Crea una copia del nuevo valor. Copia de reclamaciones de propiedad de la nueva instancia, por lo que el valor anterior se envía un lanzamiento mensaje. Esto es como una referencia fuerte a una nueva instancia del objeto. En general, la copia solo se utiliza para tipos inmutables como NSString.

Como ejemplo simple, examine la siguiente declaración de propiedad:

@ propiedad (retener) persona * capitán;

los conservar atributo le dice al asociado @sintetizar Declaración para crear un setter que se parece a algo como:

- (void) setCaptain: (Person *) theCaptain [_captain release]; _captain = [theCaptain retain]; 

Como puedes imaginar, usando atributos de gestión de memoria con @propiedad es mucho más fácil que definir manualmente los getters y setters para cada propiedad de cada clase personalizada que defina.


Conteo automático de referencias

Ahora que tiene un control sobre el conteo de referencias, la propiedad de objetos y los bloques de liberación automática, puede olvidarse por completo de todo esto. A partir de Xcode 4.2 y iOS 4, Objective-C admite el conteo automático de referencias (ARC), que es un paso de precompilación que agrega las llamadas necesarias de administración de memoria para usted.

Si por casualidad ha desactivado ARC en la sección anterior, debe volver a encenderlo. Recuerde que puede hacer esto haciendo clic en el HelloObjectiveC proyecto en el panel de navegación, seleccionando Configuraciones de compilación pestaña, y buscando recuento automático de referencias.

Habilitando el conteo automático de referencias en la configuración de compilación del proyecto

El conteo automático de referencias funciona al examinar su código para determinar cuánto tiempo necesita un objeto para quedarse e insertarlo conservar, lanzamiento, y autorelease métodos para asegurar que se desasigne cuando ya no se necesita, pero no mientras se usa. Para no confundir el algoritmo ARC, no debe hacer cualquier conservar, lanzamiento, o autorelease llama a ti mismo Por ejemplo, con ARC, puede escribir el siguiente método y ninguno el barco ni el capitán se filtrará, aunque no renunciamos explícitamente a la propiedad de ellos:

Ejemplo de código incluido: ARC

+ (Ship *) ship Ship * theShip = [[Ship alloc] init]; Persona * theCaptain = [[Person alloc] init]; [theShip setCaptain: theCaptain]; devuelve la nave; 

Atributos ARC

En un entorno ARC, ya no debe utilizar el asignar y conservar atributos de propiedad. En su lugar, debe utilizar el débiles y fuerte atributos:

  • débiles - Especifique una relación no propietaria para el objeto de destino. Esto es muy parecido a asignar; sin embargo, tiene la funcionalidad conveniente de configurar la propiedad para nulo si el valor es desasignado De esta manera, su programa no se bloqueará cuando intente acceder a una dirección de memoria no válida.
  • fuerte - Especifique una relación de propiedad con el objeto de destino. Este es el equivalente de ARC de conservar. Asegura que un objeto no se lanzará mientras esté asignado a la propiedad..

Puede ver la diferencia entre débil y fuerte utilizando la implementación del enviar Método de clase de la sección anterior. Para crear una fuerte referencia al capitán del barco, la interfaz para Enviar Debe parecerse a lo siguiente:

// Ship.h #import "Person.h" @interface Ship: NSObject @property (strong) Person * captain; + (Barco *) barco; @fin

Y la implementacion Enviar debe verse como

// Ship.m #import "Ship.h" @implementation Ship @synthesize captain = _captain; + (Ship *) ship Ship * theShip = [[Ship alloc] init]; Persona * theCaptain = [[Person alloc] init]; [theShip setCaptain: theCaptain]; devuelve la nave;  @final

Entonces, puedes cambiar main.m para mostrar el capitán del barco:

int main (int argc, const char * argv []) @autoreleasepool Ship * ship = [Ship ship]; NSLog (@ "% @", [capitán de barco]);  devuelve 0; 

Esto producirá algo como en la consola, que nos dice que la el capitán objeto creado en el enviar el método de clase todavía existe.

Pero, intenta cambiar la (fuerte) atributo de propiedad a (débiles) y volver a compilar el programa. Ahora, deberías ver (nulo) en el panel de salida. La débil referencia no asegura que la el capitán variable se pega alrededor, así que una vez que llega al final de la enviar método de clase, el algoritmo ARC piensa que puede disponer de el capitán. Como resultado, la capitán la propiedad se establece en nulo.


Resumen

La administración de la memoria puede ser una molestia, pero es una parte esencial de la creación de una aplicación. Para las aplicaciones iOS, la asignación / eliminación adecuada de objetos es particularmente importante debido a los recursos de memoria limitados de los dispositivos móviles. Hablaremos más sobre esto en la segunda parte de esta serie., iOS sucintamente.

Afortunadamente, el nuevo esquema ARC facilita mucho más la administración de la memoria en el desarrollador promedio. En la mayoría de los casos, es posible tratar un proyecto ARC como la recolección de basura en un programa de C #; simplemente cree sus objetos y deje que ARC los elimine a su discreción. Sin embargo, tenga en cuenta que esto es simplemente una similitud práctica: la implementación de ARC es mucho más eficiente que la recolección de basura..

Esta lección representa un capítulo de Objective-C Sucintamente, un libro electrónico gratuito del equipo de Syncfusion.