Comprender el recuento de referencias con cacao y Objective-C


122

Estoy empezando a echar un vistazo a Objective-C y Cocoa para jugar con el iPhone SDK. Estoy razonablemente cómodo con las C mallocy el freeconcepto, pero el esquema de conteo de referencias de Cocoa me tiene bastante confundido. Me han dicho que es muy elegante una vez que lo entiendes, pero todavía no he superado el problema.

¿De qué manera release, retainy autoreleaseel trabajo y cuáles son las convenciones sobre su uso?

(O en su defecto, ¿qué leíste que te ayudó a conseguirlo?)

Respuestas:


148

Comencemos con retainy release; autoreleasees realmente solo un caso especial una vez que entiendes los conceptos básicos.

En Cocoa, cada objeto realiza un seguimiento de cuántas veces se hace referencia (específicamente, la NSObjectclase base implementa esto). Al invocar retainun objeto, le está diciendo que desea aumentar su recuento de referencia en uno. Al llamar release, le dice al objeto que lo está soltando, y su recuento de referencia disminuye. Si, después de llamar release, el recuento de referencia ahora es cero, entonces el sistema libera la memoria de ese objeto.

La forma básica en que esto difiere mallocy freees que cualquier objeto dado no necesita preocuparse por otras partes del sistema que fallan porque ha liberado la memoria que estaban usando. Suponiendo que todos están jugando y reteniendo / liberando de acuerdo con las reglas, cuando una pieza de código retiene y luego libera el objeto, cualquier otra pieza de código que también haga referencia al objeto no se verá afectada.

Lo que a veces puede ser confuso es conocer las circunstancias bajo las cuales debe llamar retainy release. Mi regla general es que si quiero aferrarme a un objeto por un período de tiempo prolongado (por ejemplo, si es una variable miembro en una clase), entonces debo asegurarme de que el recuento de referencias del objeto me conozca. Como se describió anteriormente, el recuento de referencia de un objeto se incrementa al llamar retain. Por convención, también se incrementa (se establece en 1, realmente) cuando el objeto se crea con un método "init". En cualquiera de estos casos, es mi responsabilidad llamar releaseal objeto cuando haya terminado con él. Si no lo hago, habrá una pérdida de memoria.

Ejemplo de creación de objetos:

NSString* s = [[NSString alloc] init];  // Ref count is 1
[s retain];                             // Ref count is 2 - silly
                                        //   to do this after init
[s release];                            // Ref count is back to 1
[s release];                            // Ref count is 0, object is freed

Ahora para autorelease. La liberación automática se usa como una forma conveniente (y a veces necesaria) de decirle al sistema que libere este objeto después de un tiempo. Desde una perspectiva de fontanería, cuando autoreleasese llama, los hilos actuales NSAutoreleasePoolson alertados de la llamada. El NSAutoreleasePoolahora sabe que una vez que llega una oportunidad (después de la iteración actual del bucle de eventos), se puede llamar releaseen el objeto. Desde nuestra perspectiva como programadores, se encarga de llamarnos release, por lo que no tenemos que hacerlo (y de hecho, no deberíamos).

Es importante tener en cuenta que (de nuevo, por convención) todos los métodos de clase de creación de objetos devuelven un objeto lanzado automáticamente. Por ejemplo, en el siguiente ejemplo, la variable "s" tiene un recuento de referencia de 1, pero una vez que se completa el bucle de eventos, se destruirá.

NSString* s = [NSString stringWithString:@"Hello World"];

Si desea colgarse de esa cadena, deberá llamar retainexplícitamente y luego explícitamente releasecuando haya terminado.

Considere el siguiente bit (muy artificial) de código, y verá una situación en la que autoreleasese requiere:

- (NSString*)createHelloWorldString
{
    NSString* s = [[NSString alloc] initWithString:@"Hello World"];

    // Now what?  We want to return s, but we've upped its reference count.
    // The caller shouldn't be responsible for releasing it, since we're the
    // ones that created it.  If we call release, however, the reference 
    // count will hit zero and bad memory will be returned to the caller.  
    // The answer is to call autorelease before returning the string.  By 
    // explicitly calling autorelease, we pass the responsibility for
    // releasing the string on to the thread's NSAutoreleasePool, which will
    // happen at some later time.  The consequence is that the returned string 
    // will still be valid for the caller of this function.
    return [s autorelease];
}

Me doy cuenta de que todo esto es un poco confuso; sin embargo, en algún momento hará clic. Aquí hay algunas referencias para comenzar:

  • Introducción de Apple a la gestión de memoria.
  • Cocoa Programming para Mac OS X (4a edición) , de Aaron Hillegas, un libro muy bien escrito con muchos ejemplos excelentes. Se lee como un tutorial.
  • Si realmente te estás sumergiendo, puedes dirigirte a Big Nerd Ranch . Este es un centro de capacitación dirigido por Aaron Hillegas, el autor del libro mencionado anteriormente. Asistí al curso de Introducción al Cacao allí hace varios años, y fue una excelente manera de aprender.

8
Usted escribió: "Al llamar a autorelease, aumentamos temporalmente el recuento de referencias". Creo que esto está mal; Autorrelease solo marca el objeto que se lanzará en el futuro, no aumenta el recuento de referencias: cocoadev.com/index.pl?AutoRelease
LKM

2
"Ahora para la liberación automática. La liberación automática se usa como una forma conveniente (y a veces necesaria) de decirle al sistema que libere este objeto después de un tiempo". Como oración inicial, esto está mal. No le dice al sistema que "lo libere", le dice que disminuya el conteo de retención.
mmalc

3
Muchas gracias por la buena explicación. Solo una cosa que aún no está clara. Si NSString* s = [[NSString alloc] initWithString:@"Hello World"];devuelve un objeto lanzado automáticamente (mientras lo escribe), ¿por qué tengo que hacer un return [s autorelease];y configurarlo "lanzamiento automático" nuevamente y no solo return s?
znq

3
@Stefan: [[NSString alloc] initWithString:@"Hello World"]NO devolverá un objeto lanzado automáticamente. Cada vez que allocse llama, el recuento de referencia se establece en 1, y es responsabilidad de ese código asegurarse de que se libere. La [NSString stringWithString:]llamada, por el contrario, no devolver un objeto autoreleased.
Matt Dillard

66
Curiosidades divertidas: dado que la respuesta usa @ "" y NSString, las cadenas son constantes y, por lo tanto, el recuento absoluto de retención será constante y completamente irrelevante ... no hace que la respuesta sea incorrecta, de ninguna manera, solo refuerza el hecho de que los recuentos de retención absoluta nunca son realmente algo de lo que deba preocuparse.
bbum

10

Si comprende el proceso de retención / liberación, entonces hay dos reglas de oro que son "duh" obvias para los programadores establecidos de Cocoa, pero desafortunadamente rara vez se explican claramente para los recién llegados.

  1. Si una función que devuelve un objeto tiene alloc, createo copyen su nombre, entonces el objeto es suyo. Debe llamar [object release]cuando haya terminado. O CFRelease(object), si es un objeto Core-Foundation.

  2. Si NO tiene una de estas palabras en su nombre, entonces el objeto pertenece a otra persona. Debe llamar [object retain]si desea conservar el objeto después del final de su función.

Le convendría seguir también esta convención en las funciones que cree usted mismo.

(Nitpickers: Sí, desafortunadamente hay algunas llamadas API que son excepciones a estas reglas pero son raras).


11
Esto es incompleto e inexacto. Sigo sin entender por qué las personas intentan repetir las reglas en lugar de simplemente señalar la documentación relevante: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
mmalc

44
Las reglas de la Fundación Core en particular son diferentes de las de Cocoa; ver developer.apple.com/documentation/CoreFoundation/Conceptual/…
mmalc

1
Yo tampoco estoy de acuerdo. Si una función devuelve algo que no quiere poseer, debería liberarla automáticamente. Es el llamador del trabajo de funciones retenerlo (si lo desea). No debería tener NADA que ver con el nombre de cualquier método que se invoque. Esa es más codificación de estilo C donde la propiedad de los objetos no está clara.
Sam

1
¡Lo siento! Creo que fui apresurado en la votación negativa. Reglas de administración de memoria Su respuesta casi cita el documento de Apple.
Sam

8

Si está escribiendo código para el escritorio y puede apuntar a Mac OS X 10.5, al menos debería considerar el uso de la recolección de basura Objective-C. Realmente simplificará la mayor parte de su desarrollo, es por eso que Apple puso todo el esfuerzo en crearlo en primer lugar y hacer que funcione bien.

En cuanto a las reglas de administración de memoria cuando no se usa GC:

  • Si crea un nuevo objeto utilizando +alloc/+allocWithZone:, +new, -copyo -mutableCopy, o si -retainun objeto, que está tomando posesión de ella y debe asegurarse de que se envíe -release.
  • Si recibe un objeto de cualquier otra manera, usted es no el propietario de la misma y debe no asegurarse de que se envíe -release.
  • Si desea asegurarse de que se envíe un objeto -release, puede enviarlo usted mismo, o puede enviar el objeto -autoreleasey el grupo de liberación automática actual lo enviará -release(una vez por recibido -autorelease) cuando se agote el grupo.

Por -autoreleaselo general, se usa como una forma de garantizar que los objetos vivan durante el evento actual, pero que luego se limpien, ya que existe un grupo de liberación automática que rodea el procesamiento de eventos de Cocoa. En Cocoa, es mucho más común devolver objetos a una persona que llama que se han lanzado automáticamente que devolver objetos que la persona que llama necesita liberar.


6

Objective-C utiliza el recuento de referencias , lo que significa que cada objeto tiene un recuento de referencias. Cuando se crea un objeto, tiene un recuento de referencia de "1". Simplemente hablando, cuando se hace referencia a un objeto (es decir, se almacena en algún lugar), se "retiene", lo que significa que su número de referencia aumenta en uno. Cuando un objeto ya no es necesario, se "libera", lo que significa que su recuento de referencia se reduce en uno.

Cuando el recuento de referencia de un objeto es 0, el objeto se libera. Este es el recuento de referencia básico.

Para algunos lenguajes, las referencias aumentan y disminuyen automáticamente, pero el objetivo-c no es uno de esos lenguajes. Por lo tanto, el programador es responsable de retener y liberar.

Una forma típica de escribir un método es:

id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;

El problema de tener que recordar liberar los recursos adquiridos dentro del código es tedioso y propenso a errores. Objective-C presenta otro concepto destinado a hacer esto mucho más fácil: las agrupaciones de liberación automática. Los grupos de liberación automática son objetos especiales que se instalan en cada subproceso. Son una clase bastante simple, si buscas NSAutoreleasePool.

Cuando un objeto recibe un mensaje de "liberación automática", el objeto buscará cualquier grupo de liberación automática que se encuentre en la pila para este hilo actual. Agregará el objeto a la lista como un objeto para enviar un mensaje de "liberación" en algún momento en el futuro, que generalmente es cuando se libera el grupo.

Tomando el código anterior, puede reescribirlo para que sea más corto y fácil de leer diciendo:

id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;

Debido a que el objeto se lanza automáticamente, ya no necesitamos llamarlo explícitamente "release". Esto se debe a que sabemos que algún grupo de liberación automática lo hará por nosotros más tarde.

Espero que esto ayude. El artículo de Wikipedia es bastante bueno sobre el recuento de referencias. Puede encontrar más información sobre las piscinas de liberación automática aquí . También tenga en cuenta que si está compilando para Mac OS X 10.5 y versiones posteriores, puede decirle a Xcode que construya con la recolección de basura habilitada, lo que le permite ignorar por completo la retención / liberación / liberación automática.


2
Esto está mal. No es necesario enviar alguna versión de objeto o publicación automática en ninguno de los ejemplos mostrados.
mmalc

6

Joshua (# 6591) - El material de recolección de basura en Mac OS X 10.5 parece bastante bueno, pero no está disponible para iPhone (o si desea que su aplicación se ejecute en versiones anteriores a 10.5 de Mac OS X).

Además, si está escribiendo una biblioteca o algo que podría reutilizarse, usar el modo GC bloquea a cualquiera que use el código para que también use el modo GC, por lo que, según tengo entendido, cualquiera que intente escribir código ampliamente reutilizable tiende a administrar memoria de forma manual.


2
Es perfectamente posible escribir un marco híbrido que admita GC y el recuento de referencias.
mmalc

6

Como siempre, cuando las personas comienzan a tratar de volver a redactar el material de referencia, casi siempre se equivocan o proporcionan una descripción incompleta.

Apple proporciona una descripción completa del sistema de administración de memoria de Cocoa en la Guía de programación de administración de memoria para Cocoa , al final de la cual hay un resumen breve pero preciso de las Reglas de administración de memoria .




2
En realidad, este es un resumen de una página mucho mejor: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…
Brian Moeskau

6

No agregaré al específico de retención / lanzamiento que no sea que desee pensar en dejar $ 50 y obtener el libro de Hillegass, pero sugeriría encarecidamente que use las herramientas de Instrumentos muy temprano en el desarrollo de su aplicación (incluso su ¡el primero!). Para hacerlo, Ejecutar-> Comenzar con herramientas de rendimiento. Comenzaría con Leaks, que es solo uno de los muchos instrumentos disponibles, pero ayudará a mostrarle cuándo se olvidó de lanzar. Deja de desalentar la cantidad de información que se te presentará. Pero revise este tutorial para levantarse e ir rápido:
TUTORIAL DEL CACAO: FIJACIÓN DE FUGAS DE MEMORIA CON INSTRUMENTOS

En realidad, tratar de forzar las fugas podría ser una mejor manera de aprender a prevenirlas. Buena suerte ;)


5

Matt Dillard escribió :

return [[s autorelease] release];

La liberación automática no retiene el objeto. Autorelease simplemente lo pone en cola para ser lanzado más tarde. No desea tener una declaración de lanzamiento allí.




4

La respuesta de NilObject es un buen comienzo. Aquí hay información adicional relacionada con la administración manual de memoria ( requerida en el iPhone ).

Si usted personalmente es alloc/initun objeto, viene con un recuento de referencia de 1. Usted es responsable de limpiarlo cuando ya no sea necesario, ya sea llamando [foo release]o [foo autorelease]. release lo limpia de inmediato, mientras que la liberación automática agrega el objeto al grupo de liberación automática, que lo liberará automáticamente más adelante.

la liberación automática es principalmente para cuando tiene un método que necesita devolver el objeto en cuestión ( por lo que no puede liberarlo manualmente, de lo contrario, devolverá un objeto nulo ) pero tampoco desea retenerlo .

Si adquiere un objeto donde no llamó a alloc / init para obtenerlo, por ejemplo:

foo = [NSString stringWithString:@"hello"];

pero desea aferrarse a este objeto, debe llamar a [foo retener]. De lo contrario, es posible que se obtenga autoreleasedy se mantenga una referencia nula (como lo haría en el stringWithStringejemplo anterior ). Cuando ya no lo necesite, llame [foo release].


2

Las respuestas anteriores dan declaraciones claras de lo que dice la documentación; El problema que enfrentan la mayoría de las personas nuevas son los casos indocumentados. Por ejemplo:

  • Lanzamiento automático : los documentos dicen que activará un lanzamiento "en algún momento en el futuro". ¡¿CUANDO?! Básicamente, puede contar con el objeto alrededor hasta que salga de su código nuevamente en el bucle de eventos del sistema. El sistema PUEDE liberar el objeto en cualquier momento después del ciclo de evento actual. (Creo que Matt dijo eso antes).

  • Cuerdas estáticas : NSString *foo = @"bar";¿tiene que retener o liberar eso? No. ¿Qué tal

    -(void)getBar {
        return @"bar";
    }

    ...

    NSString *foo = [self getBar]; // still no need to retain or release
  • La regla de creación : si la creó, es suya y se espera que la publique.

En general, la forma en que los nuevos programadores de Cocoa se equivocan es al no entender qué rutinas devuelven un objeto con a retainCount > 0.

Aquí hay un fragmento de Reglas muy simples para la administración de memoria en Cocoa :

Reglas de conteo de retención

  • Dentro de un bloque dado, el uso de -copy, -alloc y -retain debe ser igual al uso de -release y -autorelease.
  • Los objetos creados con constructores de conveniencia (p. Ej. StringWithString de NSString) se consideran liberados automáticamente.
  • Implemente un método -dealloc para liberar las variables de instancia que posee

La primera viñeta dice: si llamó alloc(o new fooCopy), debe llamar a release en ese objeto.

La segunda viñeta dice: si usa un constructor de conveniencia y necesita que el objeto cuelgue (como con una imagen que se dibujará más adelante), debe retenerlo (y luego liberarlo).

El tercero debe explicarse por sí mismo.


"Autorelease: los documentos dicen que desencadenará un lanzamiento" en algún momento en el futuro "¿CUÁNDO? Los documentos son claros en ese punto: "liberación automática solo significa" enviar un mensaje de liberación más tarde "(para obtener una definición de más tarde, consulte" Grupos de liberación automática ")". Exacely cuando depende de la pila piscina autorelease ...
mmalc

... "El sistema PUEDE liberar el objeto en cualquier momento después del ciclo de evento actual". Esto hace que el sistema suene bastante menos determinista de lo que es ...
mmalc

... NSString foo = [self getBar]; // todavía no es necesario retener o liberar Esto está mal. Quien invoca getBar no conoce los detalles de implementación, por lo que * debe retener / liberar (generalmente a través de los accesores) si quieren usarlo fuera del alcance actual.
mmalc

El artículo "Reglas muy simples para la gestión de la memoria en el cacao" está desactualizado en varios aspectos, en particular, "Los objetos creados utilizando constructores convenientes (por ejemplo, stringWithString de NSString) se consideran auto liberados". no es correcto, simplemente "no es propiedad del destinatario".
mmalc


0

Como ya mencionaron varias personas, la Introducción a la administración de memoria de Apple es, con mucho, el mejor lugar para comenzar.

Un enlace útil que no he visto mencionado aún es Practical Memory Management . Lo encontrará en el medio de los documentos de Apple si los lee, pero vale la pena vincularlos directamente. Es un resumen ejecutivo brillante de las reglas de administración de memoria con ejemplos y errores comunes (básicamente, lo que otras respuestas están tratando de explicar, pero no tan bien).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.