¿Por qué todavía se necesita @autoreleasepool con ARC?

191

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

@autoreleasepool {
    
}

Mi pregunta es, ¿por qué necesitaría esto cuando se supone que no debo lanzar / liberar automáticamente?


EDITAR: Para resumir lo que obtuve de todas las respuestas y comentarios sucintamente:

Nueva sintaxis:

@autoreleasepool { … } es nueva sintaxis para

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

[pool drain];

Más importante:

  • ARC utiliza autoreleasetan bien como release.
  • Necesita un grupo de liberación automática para hacerlo.
  • ARC no crea el grupo de lanzamiento automático para usted. Sin embargo:
    • El hilo principal de cada aplicación de Cocoa ya tiene un grupo de liberación automática.
  • Hay dos ocasiones en las que es posible que desee utilizar @autoreleasepool:
    1. Cuando se encuentra en un subproceso secundario y no hay un grupo de liberación automática, debe hacer el suyo para evitar fugas, como myRunLoop(…) { @autoreleasepool { … } return success; }.
    2. Cuando desee crear un grupo más local, como ha mostrado @mattjgalloway en su respuesta.
mk12
fuente
1
También hay una tercera ocasión: cuando desarrollas algo que no está relacionado con UIKit o NSFoundation. Algo que usa herramientas de línea de comando más o menos
Garnik

Respuestas:

214

ARC no elimina las retenciones, los lanzamientos y los lanzamientos automáticos, solo agrega los necesarios para usted. Así que todavía hay llamadas para retener, todavía hay llamadas para liberar, todavía hay llamadas para liberar automáticamente y todavía hay grupos de liberación automática.

Uno de los otros cambios que hicieron con el nuevo compilador Clang 3.0 y ARC es que reemplazaron NSAutoReleasePoolcon la @autoreleasepooldirectiva del compilador. NSAutoReleasePoolsiempre fue un poco un "objeto" especial y lo lograron para que la sintaxis de usar uno no se confunda con un objeto, por lo que generalmente es un poco más simple.

Básicamente, lo necesitas @autoreleasepoolporque todavía hay grupos de liberación automática de los que preocuparte. Simplemente no necesita preocuparse por agregar autoreleasellamadas.

Un ejemplo de uso de un grupo de liberación automática:

- (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 ideado, claro, pero si no tuvieras el @autoreleasepoolinterior del forbucle externo , estarías liberando 100000000 objetos más tarde que 10000 cada vez alrededor del forbucle externo .

Actualización: Consulte también esta respuesta: https://stackoverflow.com/a/7950636/1068248 , para @autoreleasepoolsaber por qué no tiene nada que ver con ARC.

Actualización: eché un vistazo a lo interno de lo que está sucediendo aquí y lo escribí en mi blog . Si echas un vistazo allí, verás exactamente qué está haciendo ARC y cómo @autoreleasepoolel compilador utiliza el nuevo estilo y cómo introduce un alcance para inferir información sobre qué retenciones, lanzamientos y lanzamientos automáticos son necesarios.

mattjgalloway
fuente
11
No elimina las retenciones. Los agrega para ti. El conteo de referencias todavía está entrando, es simplemente automático. Por lo tanto, cuenta de referencia automática :-D.
mattjgalloway
66
Entonces, ¿por qué no agrega el @autoreleasepoolpara mí también? Si no estoy controlando lo que se libera o libera automáticamente (ARC lo hace por mí), ¿cómo debo saber cuándo configurar un grupo de liberación automática?
mk12
55
Pero usted tiene control sobre dónde permanecen sus grupos de liberación automática. Hay uno envuelto alrededor de toda su aplicación de forma predeterminada, pero es posible que desee más.
mattjgalloway
55
Buena pregunta. Solo tienes que "saber". Piense en agregar uno como algo similar a por qué uno podría, en un lenguaje GC, agregar una pista a un recolector de basura para continuar y ejecutar un ciclo de recolección ahora. Tal vez sepa que hay una tonelada de objetos listos para ser eliminados, tiene un bucle que asigna un montón de objetos temporales, por lo que "sabe" (o los instrumentos podrían decirle :) que agregar un grupo de liberación alrededor del bucle sería un buena idea.
Graham Perks el
66
El ejemplo de bucle funciona perfectamente bien sin liberación automática: cada objeto se desasigna cuando la variable se sale del alcance. Ejecutar el código sin liberación automática requiere una cantidad constante de memoria y muestra que los punteros se reutilizan, y poner un punto de interrupción en el dealloc de un objeto muestra que se llama una vez cada vez a través del bucle, cuando se llama objc_storeStrong. Tal vez OSX hace algo tonto aquí, pero Autoreleasepool es completamente innecesario en iOS.
Glenn Maynard
16

@autoreleasepoolno libera automáticamente nada. Crea un grupo de liberación automática, de modo que cuando se alcanza el final del bloque, cualquier objeto que haya sido liberado automáticamente por ARC mientras el bloque estaba activo recibirá mensajes de liberación. La Guía de programación de administración de memoria avanzada de Apple lo explica así:

Al final del bloque de agrupación 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 por cada vez que se envió un mensaje de liberación automática dentro del bloque.

outis
fuente
1
No necesariamente. El objeto recibirá un releasemensaje, pero si el recuento de retención es> 1, el objeto NO será desasignado.
andybons
@andybons: actualizado; Gracias. ¿Es esto un cambio del comportamiento anterior al ARC?
outis
Esto es incorrecto. Los objetos lanzados por ARC recibirán mensajes de liberación tan pronto como sean lanzados por ARC, con o sin un grupo de liberación automática.
Glenn Maynard
7

Las personas a menudo malinterpretan ARC para 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 (todo retainsy releases, etc.) puede automatizarse completamente en tiempo de compilación . Esto es, solo 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 compilador no podría automatizar el proceso en el momento de la compilación. Por lo tanto, para asegurar que nunca se romper las reglas, no se nos permite escribir de forma explícita release, retainetc. Esas llamadas se inyectan automáticamente en nuestro código por el compilador. Por lo tanto internamente todavía tenemos autoreleases, retain, release, etc. Es simplemente que no es necesario escribirlos más.

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

Todavía lo tenemos @autoreleasepool{...}porque tenerlo no rompe ninguna de las reglas, somos libres de crear / drenar nuestro grupo cada vez que lo necesitemos :).

nacho4d
fuente
1
ARC es GC de conteo de referencias, no GC de marcado y barrido como se obtiene en JavaScript y Java, pero definitivamente es recolección de basura. Esto no responde a la pregunta: "usted puede" no responde a la pregunta de "por qué debería". No deberías
Glenn Maynard
3

Esto se debe a que aún debe proporcionar al compilador pistas sobre cuándo es seguro que los objetos liberados automáticamente salgan del alcance.

DougW
fuente
¿Me puede dar un ejemplo de cuándo necesitaría hacer esto?
mk12
Entonces, antes de ARC, por ejemplo, tenía un CVDisplayLink ejecutándose en un subproceso secundario para mi aplicación OpenGL, pero no creé un grupo de liberación automática en su runloop porque sabía que no estaba revelando automáticamente nada (o usando bibliotecas que sí lo hacen). ¿ @autoreleasepoolEso significa que ahora necesito agregar porque no sé si ARC podría decidir liberar automáticamente algo?
mk12
@ Mk12 - No. Siempre tendrá un grupo de liberación automática que se agota cada vez que se ejecuta el ciclo de ejecución principal. Solo debe agregar uno cuando desee asegurarse de que los objetos que se han liberado automáticamente se drenen antes de lo contrario, por ejemplo, la próxima vez que se ejecute el ciclo de ejecución.
mattjgalloway
2
@DougW: eché un vistazo a lo que realmente está haciendo el compilador y escribí un blog al respecto aquí: iphone.galloway.me.uk/2012/02/a-look-under-arcs-hood- –-episode-3 /. Esperemos que explique lo que sucede tanto en tiempo de compilación como en tiempo de ejecución.
mattjgalloway 01 de
2

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

Bloques e hilos de agrupación de liberación automática

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

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

Nota: Si crea hilos secundarios usando las API de hilos POSIX en lugar de NSThread, no puede usar Cocoa a menos que Cocoa esté en modo de subprocesos múltiples. Cocoa ingresa al modo de subprocesos múltiples solo después de separar su primer objeto NSThread. Para usar 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 de subprocesos múltiples con el método de clase NSThread isMultiThreaded.

...

En el Conteo automático de referencias, o ARC, el sistema usa el mismo sistema de conteo de referencias que MRR, pero inserta las llamadas de método de administración de memoria apropiadas para usted en tiempo de compilación. Se le recomienda utilizar ARC para nuevos proyectos. Si usa ARC, generalmente 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 las Notas de versión de ARC.

Raunak
fuente
2

Se requieren agrupaciones de liberación automática para devolver objetos recién creados de un método. Por ejemplo, considere este fragmento de código:

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

La cadena creada en el método tendrá un recuento de retención de uno. Ahora, ¿quién equilibrará ese conteo 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 devolverlo.

La persona 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 cree un nuevo objeto, solo dice que se devuelve un objeto y este objeto devuelto puede ser uno nuevo que requiera una liberación, pero puede Bueno, sea uno existente que no lo hace. Lo que el método devuelve 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 importarle.

Si la persona que llama siempre tiene que liberar todos los objetos devueltos por convención, entonces todos los objetos que no se crearon siempre tendrían que retenerse antes de devolverlo de un método y la persona que llama tendría que liberarlos una vez que salga del alcance, a menos que Se devuelve de nuevo. Esto sería muy ineficiente en muchos casos, ya que se 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 liberación automática, por lo que el primer método de hecho se convertirá

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

Llamando autoreleasesobre un objeto lo añade a la piscina autorelease, pero ¿qué significa eso realmente, la adición de un objeto a la piscina autorelease? Bueno, significa decirle a su sistema " Quiero que libere ese objeto por mí, pero en algún momento posterior, no ahora; tiene un recuento de retención que necesita ser equilibrado por una liberación; de lo contrario, la memoria se perderá, pero no puedo hacerlo yo mismo en este momento, ya que necesito que el objeto permanezca vivo más allá de mi alcance actual y mi interlocutor 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 limpia mi objeto por mí " .

Con ARC, el compilador decide cuándo conservar 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 hacer 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 la persona que llama como la persona que llama estén usando ARC (recuerde que mezclar ARC y no ARC es legal y también oficialmente compatible) y, si ese es 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 códigos ARC y no ARC):

// Callee
- (SomeObject *)getSomeObject {
    return [[[SomeObject alloc] init] autorelease];
}

// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];

(Tenga en cuenta que la retención / liberación en la persona que llama es solo una retención de seguridad defensiva, no es estrictamente necesario, el código sería perfectamente correcto sin él)

O puede comportarse como este código, en caso de que se detecte que ambos usan 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 de aire, por lo tanto, también la liberación retardada de objetos cuando se destruye el grupo, así como la retención de seguridad. Para obtener más información sobre cómo es posible y lo que realmente sucede detrás de escena, consulte esta publicación de blog.

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

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

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

Suponga que cada llamada a tempObjectForDatapuede crear una nueva TempObjectque se devuelve automáticamente. El ciclo for creará un millón de estos objetos temporales que se recopilarán en el grupo de liberación automática actual y solo una vez que se destruya ese grupo, también se destruirán todos los objetos temporales. Hasta que eso suceda, tiene un millón de estos objetos temporales en la memoria.

Si escribes el código de esta manera:

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

Luego, se crea un nuevo grupo cada vez que se ejecuta el ciclo for y se destruye al final de cada iteración del ciclo. De esa manera, a lo sumo, un objeto temporal permanece en la memoria en cualquier momento a pesar del ciclo que se ejecuta un millón de veces.

En el pasado, a menudo también tenía que administrar las agrupaciones de liberación automática cuando administraba subprocesos (por ejemplo, usar NSThread), ya que solo el subproceso principal tiene automáticamente un grupo de liberación automática para una aplicación Cocoa / UIKit. Sin embargo, esto es más o menos el legado de hoy, ya que hoy probablemente no usaría hilos para empezar. Usaría GCD DispatchQueue's o NSOperationQueue' s y estos dos sí administran un grupo de liberación automática de nivel superior para usted, creado antes de ejecutar un bloque / tarea y destruido una vez hecho con él.

Mecki
fuente
-4

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

Si un proyecto (incluidas sus dependencias) usa exclusivamente ARC, entonces @autoreleasepool nunca necesita ser usado y no hará nada útil. ARC manejará la liberación de 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 tan pronto como el valor queda fuera de alcance, sin esperar a que salga un grupo de liberación automática. (Lo mismo sucede con el ejemplo NSNumber; esto simplemente nos permite observar el acuerdo.) ARC no utiliza la liberación automática.

La razón por la que @autoreleasepool todavía está permitida es para proyectos mixtos ARC y no ARC, que aún no se han transferido completamente a ARC.

Si llama a código no-ARC, se puede devolver un objeto autoreleased. En ese caso, el bucle anterior se filtraría, ya que el grupo de liberación automática actual nunca se cerrará. Ahí es donde querría poner un @autoreleasepool alrededor del bloque de código.

Pero si ha realizado la transición ARC por completo, entonces olvídese de autoreleasepool.

Glenn Maynard
fuente
44
Esta respuesta es incorrecta y también va en contra de la documentación ARC. su evidencia es anecdótica porque está utilizando un método de asignación que el compilador decide no liberar automáticamente. Puede ver fácilmente que esto no funciona si crea un nuevo inicializador estático para su clase personalizada. Cree este inicializador y utilizarlo en su bucle: + (Testing *) testing { return [Testing new] }. Entonces verás que no se llamará a dealloc hasta más tarde. Esto se soluciona si envuelve el interior del bucle en un @autoreleasepoolbloque.
Dima
@Dima Probado en iOS10, se llama a dealloc inmediatamente después de imprimir la dirección del objeto. + (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
KudoCC
@KudoCC - Yo también, y vi el mismo comportamiento que tú. Pero, cuando me lancé [UIImage imageWithData]a la ecuación, de repente, comencé a ver el autoreleasecomportamiento tradicional , que requería @autoreleasepoolmantener la memoria máxima a un nivel razonable.
Rob
@Rob No puedo evitar agregar el enlace .
KudoCC