Os pools de liberação automática são necessários para retornar objetos recém-criados de um método. Por exemplo, considere este pedaço de código:
- (NSString *)messageOfTheDay {
return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}
A sequência criada no método terá uma contagem de retenção de um. Agora, quem equilibrará os que mantêm a conta com uma liberação?
O método em si? Não é possível, ele precisa retornar o objeto criado, portanto, não deve liberá-lo antes de retornar.
O chamador do método? O chamador não espera recuperar um objeto que precise ser liberado, o nome do método não implica que um novo objeto seja criado, apenas diz que um objeto é retornado e esse objeto retornado pode ser um novo que requer liberação, mas pode bem ser um existente que não. O retorno do método pode até depender de algum estado interno; portanto, o chamador não pode saber se precisa liberar esse objeto e não deve se preocupar.
Se o chamador sempre liberasse todos os objetos retornados por convenção, todos os objetos não criados recentemente sempre teriam que ser retidos antes de retorná-los de um método e teriam que ser liberados pelo chamador assim que sair do escopo, a menos que é retornado novamente. Isso seria altamente ineficiente em muitos casos, pois é possível evitar completamente alterar as contagens de retenção em muitos casos, se o chamador nem sempre liberar o objeto retornado.
É por isso que existem pools de autorelease, para que o primeiro método se torne realmente
- (NSString *)messageOfTheDay {
NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
return [res autorelease];
}
A chamada autorelease
de um objeto o adiciona ao pool de liberação automática, mas o que isso realmente significa, adicionando um objeto ao pool de liberação automática? Bem, significa dizer ao seu sistema " Eu quero que você libere esse objeto para mim, mas em algum momento posterior, não agora; ele tem uma contagem de retenção que precisa ser equilibrada por um release, caso contrário a memória vazará, mas eu não posso fazer isso sozinho No momento, como preciso que o objeto permaneça vivo além do meu escopo atual e meu interlocutor também não fará isso por mim, ele não tem conhecimento de que isso precisa ser feito.Então, adicione-o ao seu pool e depois de limpar o piscina, também limpe meu objeto para mim. "
Com o ARC, o compilador decide quando reter um objeto, quando liberar um objeto e quando adicioná-lo a um pool de liberação automática, mas ainda exige a presença de pools de liberação automática para poder retornar objetos recém-criados de métodos sem perda de memória. A Apple acaba de fazer algumas otimizações bacanas no código gerado, que às vezes eliminam os pools de liberação automática durante o tempo de execução. Essas otimizações exigem que ambos, o chamador e o destinatário estejam usando o ARC (lembre-se de misturar ARC e não-ARC é legal e também oficialmente suportado) e, se esse for realmente o caso, só poderá ser conhecido em tempo de execução.
Considere este código ARC:
// Callee
- (SomeObject *)getSomeObject {
return [[SomeObject alloc] init];
}
// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
O código que o sistema gera, pode se comportar como o código a seguir (que é a versão segura que permite combinar livremente código ARC e não-ARC):
// Callee
- (SomeObject *)getSomeObject {
return [[[SomeObject alloc] init] autorelease];
}
// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];
(Observe que a retenção / liberação no chamador é apenas uma retenção de segurança defensiva, não é estritamente necessária, o código estaria perfeitamente correto sem ele)
Ou pode se comportar como este código, caso ambos sejam detectados para usar o ARC em tempo de execução:
// Callee
- (SomeObject *)getSomeObject {
return [[SomeObject alloc] init];
}
// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];
Como você pode ver, a Apple elimina o lançamento ativo, assim também a liberação atrasada do objeto quando a piscina é destruída, bem como a retenção de segurança. Para saber mais sobre como isso é possível e o que realmente está acontecendo nos bastidores, confira esta postagem no blog.
Agora, para a pergunta real: por que alguém usaria @autoreleasepool
?
Para a maioria dos desenvolvedores, resta apenas um motivo hoje para usar essa construção em seu código: manter a área de memória pequena, quando aplicável. Por exemplo, considere este loop:
for (int i = 0; i < 1000000; i++) {
// ... code ...
TempObject * to = [TempObject tempObjectForData:...];
// ... do something with to ...
}
Suponha que toda chamada para tempObjectForData
possa criar um novoTempObject
que seja retornado automaticamente. O loop for criará um milhão desses objetos temporários que são todos coletados no pool de liberação automática atual e somente quando esse pool for destruído, todos os objetos temporários também serão destruídos. Até que isso aconteça, você tem um milhão desses objetos temporários na memória.
Se você escrever o código assim:
for (int i = 0; i < 1000000; i++) @autoreleasepool {
// ... code ...
TempObject * to = [TempObject tempObjectForData:...];
// ... do something with to ...
}
Em seguida, um novo pool é criado toda vez que o loop for é executado e é destruído no final de cada iteração do loop. Dessa forma, no máximo, um objeto temporário permanece na memória a qualquer momento, apesar do loop ser executado um milhão de vezes.
No passado, você também costumava gerenciar os auto-releasespools ao gerenciar threads (por exemplo, usando NSThread
), pois apenas o thread principal possui automaticamente um pool de auto-release para um aplicativo Cocoa / UIKit. No entanto, isso é praticamente um legado hoje, pois hoje você provavelmente não usaria threads para começar. Você usaria GCDs DispatchQueue
ou NSOperationQueue
's' e esses dois gerenciam um pool de liberação automática de nível superior para você, criado antes da execução de um bloco / tarefa e destruído uma vez concluído.