¿Por qué se necesita @autoreleasepool con ARC?

En su mayor parte con ARC (conteo automático de referencias), no necesitamos pensar en la gestión de la memoria en absoluto con objetos Objective-C. Ya no está permitido crear NSAutoreleasePool , sin embargo, hay una nueva syntax:

 @autoreleasepool { … } 

Mi pregunta es, ¿por qué necesitaría esto cuando no se supone que debo liberar / autorrellenar manualmente?


EDITAR: Para resumir lo que obtuve de todos los comentarios y comentarios de manera sucinta:

Nueva syntax:

@autoreleasepool { … } es una nueva syntax para

 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; … [pool drain]; 

Más importante:

  • ARC usa autorelease y release .
  • Necesita un grupo de versiones automáticas para hacerlo.
  • ARC no crea el grupo de versiones automáticas para usted. Sin embargo:
    • El hilo principal de cada aplicación Cocoa ya tiene un grupo de autorrelease.
  • En dos ocasiones, es posible que desee utilizar @autoreleasepool :
    1. Cuando se encuentre en un hilo secundario y no haya un grupo de autorizaciones automáticas, debe hacer las suyas propias para evitar fugas, como myRunLoop(…) { @autoreleasepool { … } return success; } myRunLoop(…) { @autoreleasepool { … } return success; } .
    2. Cuando desee crear un grupo más local, como @mattjgalloway ha mostrado en su respuesta.

ARC no se deshace de los retiros, lanzamientos y autorreleases, solo agrega los necesarios para usted. Por lo tanto, todavía hay llamadas para retener, todavía hay llamadas para lanzar, todavía hay llamadas para liberación automática y todavía hay grupos de versiones automáticas.

Uno de los otros cambios que hicieron con el nuevo comstackdor Clang 3.0 y ARC es que reemplazaron a NSAutoReleasePool con la directiva del comstackdor @autoreleasepool . NSAutoReleasePool siempre fue un poco un “objeto” especial de todos modos y lo hicieron de modo que la syntax de usar uno no se confunde con un objeto, por lo que en general es un poco más simple.

Entonces, básicamente, necesita @autoreleasepool porque todavía hay grupos de versiones de auto para preocuparse. Simplemente no necesita preocuparse por agregar llamadas de autorelease .

Un ejemplo de uso de un grupo de versiones automáticas:

 - (void)useALoadOfNumbers { for (int j = 0; j < 10000; ++j) { @autoreleasepool { for (int i = 0; i < 10000; ++i) { NSNumber *number = [NSNumber numberWithInt:(i+j)]; NSLog(@"number = %p", number); } } } } 

Un ejemplo enormemente artificial, claro, pero si no @autoreleasepool el @autoreleasepool dentro del exterior for -loop, estarías liberando 100000000 objetos más adelante en vez de 10000 cada vez for salga el exterior for -loop.

Actualización: También vea esta respuesta - https://stackoverflow.com/a/7950636/1068248 - por qué @autoreleasepool no tiene nada que ver con ARC.

Actualización: Eché un vistazo a los aspectos internos de lo que está sucediendo aquí y lo escribí en mi blog . Si @autoreleasepool un vistazo allí, entonces verás exactamente lo que está haciendo ARC y cómo el nuevo estilo @autoreleasepool y cómo introduce un scope es utilizado por el comstackdor para inferir información sobre lo que se requiere, liberaciones y autorreleases.

@autoreleasepool no libera automáticamente nada. Crea un grupo de autorrelease, de modo que cuando se llegue al final del bloque, cualquier objeto que haya sido liberado automáticamente por ARC mientras el bloque estaba activo recibirá mensajes de lanzamiento. La Guía de progtwigción de administración de memoria avanzada de Apple lo explica así:

Al final del bloque de bloque de liberación automática, los objetos que recibieron un mensaje de liberación automática dentro del bloque reciben un mensaje de liberación: un objeto recibe un mensaje de liberación para cada vez que se envió un mensaje de liberación automática dentro del bloque.

La gente a menudo entiende mal ARC por algún tipo de recolección de basura o similar. La verdad es que, después de un tiempo, la gente de Apple (gracias a los proyectos de llvm y clang) se dio cuenta de que la administración de memoria de Objective-C (todas las retains y releases , etc.) se puede automatizar completamente en tiempo de comstackción . Esto es, simplemente leyendo el código, ¡incluso antes de que se ejecute! 🙂

Para hacerlo solo hay una condición: DEBEMOS seguir las reglas , de lo contrario, el comstackdor no podría automatizar el proceso en tiempo de comstackción. Por lo tanto, para garantizar que nunca rompamos las reglas, no podemos escribir explícitamente la release , retain , etc. Esas llamadas son automáticamente inyectadas en nuestro código por el comstackdor. Por lo tanto, internamente todavía tenemos autorelease s, retain , release , etc. Es solo que no necesitamos escribirlos más.

La A de ARC es automática en tiempo de comstackción, que es mucho mejor que en tiempo de ejecución, como la recolección de basura.

Todavía tenemos @autoreleasepool{...} porque al no romper ninguna de las reglas, somos libres de crear / drenar nuestro grupo en cualquier momento que lo necesitemos :).

Es porque todavía necesita proporcionar al comstackdor pistas sobre cuándo es seguro que los objetos liberados automáticamente salgan del scope.

Citado de https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html :

Autorelease Pool Blocks and Threads

Cada subproceso en una aplicación Cocoa mantiene su propia stack de bloques de agrupación de liberación automática. Si está escribiendo un progtwig exclusivo de Foundation o si separa un hilo, necesita crear su propio bloque de pool de liberación automática.

Si su aplicación o thread es de larga duración y potencialmente genera una gran cantidad de objetos liberados automáticamente, debe usar bloques de grupo de liberación automática (como AppKit y UIKit do en el hilo principal); de lo contrario, los objetos liberados automáticamente se acumulan y su huella de memoria crece. Si su subproceso desconectado no hace llamadas Cocoa, no necesita usar un bloque de agrupación de liberación automática.

Nota: Si crea subprocesos secundarios utilizando las API de subprocesos de POSIX en lugar de NSThread, no puede usar Cocoa a menos que Cocoa esté en modo de subprocesamiento múltiple. Cocoa ingresa al modo multihebra solo después de separar su primer objeto NSThread. Para utilizar Cocoa en subprocesos POSIX secundarios, su aplicación primero debe separar al menos un objeto NSThread, que puede salir inmediatamente. Puede probar si Cocoa está en modo multithreading con el método de clase NSThread isMultiThreaded.

En el recuento automático de referencias, o ARC, el sistema usa el mismo sistema de recuento de referencias que MRR, pero inserta las llamadas de método de administración de memoria adecuadas en tiempo de comstackción. Le recomendamos encarecidamente que use ARC para nuevos proyectos. Si utiliza ARC, normalmente no es necesario comprender la implementación subyacente descrita en este documento, aunque en algunas situaciones puede ser útil. Para obtener más información sobre ARC, consulte Transición a Notas de versión de ARC.

Se requieren agrupaciones de autorrecuperación para devolver objetos recién creados a partir de un método. Por ejemplo, considere esta pieza de código:

 - (NSString *)messageOfTheDay { return [[NSString alloc] initWithFormat:@"Hello %@!", self.username]; } 

La cadena creada en el método tendrá un conteo de retención de uno. Ahora, ¿quién equilibrará eso para retener contar con un lanzamiento?

El método en sí? No es posible, tiene que devolver el objeto creado, por lo que no debe liberarlo antes de volver.

¿El que llama del método? La persona que llama no espera recuperar un objeto que necesita ser liberado, el nombre del método no implica que se crea un nuevo objeto, solo dice que se devuelve un objeto y este objeto devuelto puede ser uno nuevo que requiera una versión, pero puede ser bien sea uno existente que no lo hace. Lo que devuelve el método puede incluso depender de algún estado interno, por lo que la persona que llama no puede saber si tiene que liberar ese objeto y no debería tener que importarle.

Si la persona que llama tenía que soltar siempre todos los objetos devueltos por convención, entonces cada objeto no creado recientemente siempre tendría que retenerse antes de devolverlo desde un método y debería ser liberado por el llamante una vez que salga del scope, a menos que es devuelto nuevamente Esto sería altamente ineficiente en muchos casos, ya que uno puede evitar por completo alterar los recuentos de retención en muchos casos si la persona que llama no siempre libera el objeto devuelto.

Es por eso que hay grupos de autorrelease, por lo que el primer método se convertirá en realidad

 - (NSString *)messageOfTheDay { NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username]; return [res autorelease]; } 

La autorelease llamadas en un objeto lo agrega al grupo de liberación automática, pero ¿qué significa eso realmente, agregar un objeto al grupo de liberación automática? Bueno, significa decirle a tu sistema ” Quiero que liberes ese objeto para mí, pero en algún momento posterior, no ahora, tiene un conteo retenido que necesita ser equilibrado por una versión; de lo contrario, la memoria se fugará pero no puedo hacerlo yo mismo”. en este momento, ya que necesito el objeto para mantenerme vivo más allá de mi scope actual y la persona que llama tampoco lo hará por mí, no tiene conocimiento de que esto deba hacerse. Así que agréguelo a su grupo y una vez que lo limpie piscina, también limpiar mi objeto para mí.

Con ARC, el comstackdor decide cuándo retener un objeto, cuándo liberar un objeto y cuándo agregarlo a un grupo de liberación automática, pero aún requiere la presencia de grupos de liberación automática para poder devolver los objetos recién creados de los métodos sin pérdida de memoria. Apple acaba de realizar algunas optimizaciones ingeniosas para el código generado que a veces eliminará los grupos de liberación automática durante el tiempo de ejecución. Estas optimizaciones requieren que tanto el llamador como el destinatario utilicen ARC (recuerde que mezclar ARC y no ARC es legal y también es oficialmente compatible) y si eso es realmente así, el caso solo se puede conocer en tiempo de ejecución.

Considere este código ARC:

 // Callee - (SomeObject *)getSomeObject { return [[SomeObject alloc] init]; } // Caller SomeObject * obj = [self getSomeObject]; [obj doStuff]; 

El código que genera el sistema puede comportarse como el siguiente código (que es la versión segura que le permite mezclar libremente el código ARC y el código que no es ARC):

 // Callee - (SomeObject *)getSomeObject { return [[[SomeObject alloc] init] autorelease]; } // Caller SomeObject * obj = [[self getSomeObject] retain]; [obj doStuff]; [obj release]; 

(Tenga en cuenta que el retener / liberar en la persona que llama es solo un retenedor de seguridad defensivo, no es estrictamente obligatorio, el código sería perfectamente correcto sin él)

O puede comportarse como este código, en caso de que ambos sean detectados para usar ARC en tiempo de ejecución:

 // Callee - (SomeObject *)getSomeObject { return [[SomeObject alloc] init]; } // Caller SomeObject * obj = [self getSomeObject]; [obj doStuff]; [obj release]; 

Como puede ver, Apple elimina la liberación del objective, por lo tanto, también la liberación diferida del objeto cuando se destruye el grupo, así como la retención de seguridad. Para obtener más información sobre cómo es posible y qué está sucediendo detrás de escena, consulte esta publicación en el blog.

Ahora a la pregunta real: ¿Por qué uno usaría @autoreleasepool ?

Para la mayoría de los desarrolladores, solo queda una razón para usar esta construcción en su código y es mantener la huella de memoria pequeña donde corresponda. Por ejemplo, considere este ciclo:

 for (int i = 0; i < 1000000; i++) { // ... code ... TempObject * to = [TempObject tempObjectForData:...]; // ... do something with to ... } 

Supongamos que cada llamada a tempObjectForData puede crear un nuevo TempObject que se devuelve automáticamente. For-loop creará un millón de estos objetos temporales que se recostackn en el grupo de liberación automática actual y solo una vez que se destruye ese grupo, también se destruyen todos los objetos temporales. Hasta que eso suceda, tienes un millón de estos objetos temporales en la memoria.

Si escribe el código como este en su lugar:

 for (int i = 0; i < 1000000; i++) @autoreleasepool { // ... code ... TempObject * to = [TempObject tempObjectForData:...]; // ... do something with to ... } 

Entonces se crea un nuevo grupo cada vez que se ejecuta el bucle for y se destruye al final de cada iteración de bucle. De esa manera, como máximo, un objeto temporal se queda en la memoria en cualquier momento a pesar de que el bucle se ejecuta un millón de veces.

En el pasado, a menudo también tenías que administrar selfreleasepools al gestionar hilos (por ejemplo, usando NSThread ) ya que solo el hilo principal tiene automáticamente un grupo de autorrelease para una aplicación Cocoa / UIKit. Sin embargo, esto es prácticamente un legado hoy, ya que hoy probablemente no usarías hilos para empezar. Utilizaría GCD DispatchQueue o NSOperationQueue y estos dos gestionarían un pool de autorrelease de nivel superior, creado antes de ejecutar un bloque / tarea y destruido una vez hecho esto.

Parece que hay mucha confusión sobre este tema (y al menos 80 personas que probablemente ahora están confundidas acerca de esto y piensan que necesitan rociar @autoreleasepool alrededor de su código).

Si un proyecto (incluidas sus dependencias) usa exclusivamente ARC, entonces @autoreleasepool nunca necesita ser utilizado y no hará nada útil. ARC se encargará de soltar objetos en el momento correcto. Por ejemplo:

 @interface Testing: NSObject + (void) test; @end @implementation Testing - (void) dealloc { NSLog(@"dealloc"); } + (void) test { while(true) NSLog(@"p = %p", [Testing new]); } @end 

muestra:

 p = 0x17696f80 dealloc p = 0x17570a90 dealloc 

Cada objeto de prueba se desasigna en cuanto el valor sale del scope, sin esperar a que salga un grupo de autorrelease. (Lo mismo sucede con el ejemplo de NSNumber; esto solo nos permite observar el dealloc.) ARC no usa la liberación automática.

La razón por la que @autoreleasepool aún está permitido es para proyectos ARC mixtos y proyectos que no son ARC, que aún no han cambiado por completo a ARC.

Si llama al código que no es ARC, puede devolver un objeto liberado automáticamente. En ese caso, el ciclo anterior se fugaría, ya que nunca se cerrará el grupo de autorrelease actual. Ahí es donde te gustaría poner un @autoreleasepool alrededor del bloque de código.

Pero si ha realizado completamente la transición ARC, entonces olvídese de la liberación automática.