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 autorelease
sobre 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 tempObjectForData
puede crear una nueva TempObject
que 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.