Comprender el recuento de referencias con Cocoa y Objective-C

Estoy empezando a echar un vistazo a Objective-C y Cocoa con miras a jugar con el SDK de iPhone. Estoy razonablemente cómodo con el concepto malloc y free C, pero el esquema de recuento de referencias de Cocoa me tiene bastante confundido. Me dijeron que es muy elegante una vez que lo entiendes, pero aún no he superado la barrera.

¿Cómo se release , retain y autorelease trabajo y cuáles son las convenciones sobre su uso?

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

Comencemos con retain y release ; autorelease es realmente solo un caso especial una vez que comprende los conceptos básicos.

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

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

Lo que a veces puede ser confuso es conocer las circunstancias bajo las cuales debes llamar ” retain y ” release . Mi regla general es que si quiero aferrarme a un objeto durante un período de tiempo (si es una variable miembro en una clase, por ejemplo), entonces necesito asegurarme de que el recuento de referencias del objeto sabe de mí. Como se describió anteriormente, el recuento de referencias de un objeto se incrementa llamando a 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 release sobre el 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 la autorelease . Autorelease se usa como una forma conveniente (y algunas veces necesaria) de decirle al sistema que libere este objeto después de un tiempo. Desde una perspectiva de fontanería, cuando se llama a la NSAutoreleasePool se avisa a NSAutoreleasePool del subproceso actual de la llamada. El NSAutoreleasePool ahora sabe que una vez que tiene la oportunidad (después de la iteración actual del bucle de evento), puede invocar la release del objeto. Desde nuestra perspectiva como progtwigdores, se encarga de llamar a la release para nosotros, para que no tengamos que (y de hecho, no deberíamos).

Lo que es importante tener en cuenta es que (de nuevo, por convención) todos los métodos de clase de creación de objetos devuelven un objeto liberado automáticamente. Por ejemplo, en el siguiente ejemplo, la variable “s” tiene un recuento de referencia de 1, pero después de que el bucle de evento se complete, se destruirá.

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

Si desea mantener esa cadena, debe llamar retain explícitamente, y luego release explícitamente cuando haya terminado.

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

 - (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; en algún momento, sin embargo, hará clic. Aquí hay algunas referencias para que empieces:

  • Introducción de Apple a la gestión de la memoria.
  • Cocoa Programming para Mac OS X (4ª edición) , de Aaron Hillegas: un libro muy bien escrito con muchos ejemplos geniales. Se lee como un tutorial.
  • Si realmente estás buceando, podrías ir a Big Nerd Ranch . Esta es una instalación de entrenamiento dirigida por Aaron Hillegas, el autor del libro mencionado anteriormente. Asistí al curso Intro to Cocoa allí hace varios años, y fue una gran manera de aprender.

Si comprende el proceso de retener / liberar, existen dos reglas de oro que son “duh” obvias para los progtwigdores Cocoa establecidos, pero desafortunadamente raramente se explican claramente para los recién llegados.

  1. Si una función que devuelve un objeto tiene alloc , create o copy en su nombre, entonces el objeto es tuyo. Debe llamar [object release] cuando haya terminado con él. O CFRelease(object) , si se trata de un objeto Core-Foundation.

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

Sería bueno que también sigas esta convención en funciones que creas tú mismo.

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

Si está escribiendo código para el escritorio y puede orientarlo a Mac OS X 10.5, al menos debe considerar el uso de la recolección de basura de Objective-C. Realmente simplificará la mayor parte de su desarrollo, es por eso que Apple hizo todo lo posible para crearlo, y hacerlo funcionar bien.

En cuanto a las reglas de gestión de memoria cuando no se utiliza GC:

  • Si crea un nuevo objeto usando +alloc/+allocWithZone: +new , -mutableCopy o -mutableCopy o si -retain un objeto, se está tomando posesión del mismo y debe asegurarse de que se envía -release .
  • Si recibe un objeto de otra manera, no es el propietario y no debe asegurarse de que se lo envíe.
  • Si desea asegurarse de que se envía un objeto, puede enviarlo usted mismo o puede enviarlo, y el grupo de autorrelease actual lo enviará, una vez por cada recepción -autorelease cuando se -autorelease el grupo.

Normalmente, la función de -autorelease se utiliza como una forma de garantizar que los objetos vivan durante la duración del evento actual, pero se limpian después, ya que hay un grupo de autorrelease 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 liberan automáticamente que devolver objetos que la persona que llama misma necesita liberar.

Objective-C usa 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 refiere a un objeto (es decir, almacenado en algún lugar), se “retiene”, lo que significa que su recuento de referencia se incrementa en uno. Cuando un objeto ya no se necesita, se “libera”, lo que significa que su recuento de referencia se reduce en uno.

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

Para algunos idiomas, las referencias se incrementan y disminuyen automáticamente, pero objective-c no es uno de esos idiomas. Por lo tanto, el progtwigdor 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 todos los recursos adquiridos dentro del código es tedioso y propenso a errores. Objective-C introduce otro concepto que tiene como objective hacer esto mucho más fácil: Autorelease Pools. Los pools Autorelease son objetos especiales que están instalados en cada thread. Son una clase bastante simple, si busca NSAutoreleasePool.

Cuando a un objeto se le envía un mensaje de “liberación automática”, el objeto buscará todas las agrupaciones de liberación automática que se encuentren en la stack 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 en sí.

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 libera automáticamente, ya no es necesario llamar explícitamente “liberar” en él. Esto se debe a que sabemos que un grupo de autorrelease 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 acerca de los grupos de autorrelease aquí . También tenga en cuenta que si está comstackndo para Mac OS X 10.5 y posterior, puede indicarle a Xcode que construya con la recolección de elementos no deseados habilitada, lo que le permite ignorar por completo retener / liberar / liberar automáticamente.

Joshua (# 6591) – Las cosas de la recolección de basura en Mac OS X 10.5 parecen muy buenas, pero no están disponibles para el iPhone (o si quieres que tu aplicación se ejecute en versiones anteriores a la 10.5 de Mac OS X).

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

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

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

No agregaré al específico de retención / publicación más allá de lo que podría pensar acerca de perder $ 50 y obtener el libro de Hillegass, pero le sugiero encarecidamente utilizar las herramientas de Instrumentos desde el inicio de su aplicación (incluso su ¡el primero!). Para hacerlo, Ejecutar-> Comience con herramientas de rendimiento. Comenzaría con Leaks, que es solo uno de muchos de los instrumentos disponibles, pero te ayudará a mostrarte cuando hayas olvidado publicar. Desaparece la cantidad de información que se le presentará. Pero mira este tutorial para ponerte en marcha rápidamente:
TUTORIAL DE CACAO: FIJACIÓN DE FUGAS DE MEMORIA CON INSTRUMENTOS

En realidad, tratar de forzar fugas puede ser una mejor manera de, a su vez, aprender cómo prevenirlas. Buena suerte 😉

Matt Dillard escribió :

return [[autorelease liberación]];

Autorelease no retiene el objeto. Autorelease simplemente lo pone en cola para ser lanzado más tarde. No desea tener una statement de lanzamiento allí.

Mi colección habitual de artículos de gestión de memoria de cocoa:

gestión de la memoria del cocoa

Hay un screencast gratuito disponible en la red de iDeveloperTV

Gestión de memoria en Objective-C

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

Si usted personalmente alloc/init un objeto, viene con un recuento de referencia de 1. Usted es responsable de limpiar después cuando ya no es necesario, ya sea llamando al [foo release] o [foo autorelease] [foo release] [foo autorelease] . la versión lo limpia de inmediato, mientras que la liberación automática agrega el objeto al grupo de autorrelease, que lo lanzará automáticamente en un momento posterior.

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

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 autoreleased y mantendrás una referencia nula (como lo haría en el ejemplo anterior de stringWithString ). Cuando ya no lo necesite, llame a [foo release] .

Las respuestas anteriores dan una nueva explicación clara de lo que dice la documentación; el problema con el que se topan la mayoría de las personas nuevas es con los casos no documentados. Por ejemplo:

  • Autorelease : docs dicen que activará una versión “en algún momento en el futuro”. ¡¿CUANDO?! Básicamente, puede contar con el objeto que está presente hasta que salga de nuevo su código en el ciclo 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).

  • Cadenas estáticas : NSString *foo = @"bar"; – ¿tienes 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ó, la posee y se espera que la libere.

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

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

Reglas de conteo de retención

  • Dentro de un bloque determinado, el uso de -copy, -alloc y -retain debe ser igual al uso de -release y -autorelease.
  • Los objetos creados usando constructores de conveniencia (por ejemplo, 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 alloc (o new fooCopy ), necesitas llamar a liberar ese objeto.

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

El tercero debe ser autoexplicativo.

Mucha buena información sobre cocoadev también:

  • Gestión de la memoria
  • Reglas de juego

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

Un enlace útil que no he visto mencionar aún es la Gestión práctica de la memoria . Lo encontrará en el medio de los documentos de Apple si lee a través de ellos, pero vale la pena vincularlo directamente. Es un shiny resumen ejecutivo de las reglas de administración de memoria con ejemplos y errores comunes (básicamente, lo que otras respuestas intentan explicar aquí, pero no tan bien).