Por que o @autoreleasepool ainda é necessário com o ARC?

191

Na maior parte do tempo com o ARC (contagem automática de referência), não precisamos pensar em gerenciamento de memória com objetos Objective-C. Não é mais permitido criar NSAutoreleasePools, no entanto, há uma nova sintaxe:

@autoreleasepool {
    
}

Minha pergunta é: por que eu precisaria disso quando não deveria liberar / liberar manualmente manualmente?


EDIT: Para resumir o que eu tirei de todas as respostas e comentários de forma sucinta:

Nova sintaxe:

@autoreleasepool { … } é uma nova sintaxe para

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

[pool drain];

Mais importante:

  • O ARC usa autoreleasee também release.
  • Ele precisa de um pool de liberação automática para fazer isso.
  • O ARC não cria o pool de liberação automática para você. Contudo:
    • O segmento principal de todo aplicativo Cocoa já possui um pool de liberação automática.
  • Há duas ocasiões em que você pode querer usar @autoreleasepool:
    1. Quando você está em um encadeamento secundário e não há um pool de liberação automática, deve fazer o seu próprio para evitar vazamentos, como myRunLoop(…) { @autoreleasepool { … } return success; }.
    2. Quando você deseja criar um pool mais local, como @mattjgalloway mostrou em sua resposta.
mk12
fonte
1
Há também uma terceira ocasião: Quando você desenvolve algo que não está relacionado ao UIKit ou NSFoundation. Algo que usa ferramentas de linha de comando ou algo assim
Garnik

Respostas:

214

O ARC não se livra de retenções, lançamentos e autoreleases, apenas adiciona os necessários para você. Portanto, ainda há chamadas a serem retidas, ainda há chamadas a liberar, ainda há chamadas a liberação automática e ainda existem pools de liberação automática.

Uma das outras alterações que eles fizeram com o novo compilador Clang 3.0 e o ARC é que eles foram substituídos NSAutoReleasePoolpela @autoreleasepooldiretiva do compilador. NSAutoReleasePoolsempre foi um "objeto" especial de qualquer maneira e eles o criaram para que a sintaxe do uso de um não seja confundida com um objeto, de modo que geralmente é um pouco mais simples.

Então, basicamente, você precisa, @autoreleasepoolporque ainda existem pools de liberação automática para se preocupar. Você não precisa se preocupar em adicionar autoreleasechamadas.

Um exemplo de uso de um pool de liberação 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);
            }
        }
    }
}

Um exemplo bastante artificial, com certeza, mas se você não tivesse o @autoreleasepoolinterior do forloop externo, liberaria 100000000 objetos posteriormente, em vez de 10000 a cada vez que o forloop externo fosse lançado .

Atualização: veja também esta resposta - https://stackoverflow.com/a/7950636/1068248 - por que @autoreleasepoolnada tem a ver com o ARC.

Atualizar: dei uma olhada nas informações internas do que está acontecendo aqui e escrevi no meu blog . Se você der uma olhada lá, verá exatamente o que o ARC está fazendo e como o novo estilo @autoreleasepoole como ele introduz um escopo é usado pelo compilador para inferir informações sobre o que retém, liberações e autoreleases são necessários.

mattjgalloway
fonte
11
Não se livra de retém. Adiciona-os para você. A contagem de referência ainda está entrando, é apenas automática. Daí a contagem automática de referência :-D.
precisa saber é o seguinte
6
Então, por que não acrescenta isso @autoreleasepoolpara mim também? Se não estiver controlando o que é liberado ou liberado automaticamente (o ARC faz isso por mim), como devo saber quando configurar um pool de autorelease?
Mk12
5
Mas você tem controle sobre onde seus pools de liberação automática ficam imóveis. Por padrão, existe uma envolvida em todo o aplicativo, mas você pode querer mais.
precisa saber é o seguinte
5
Boa pergunta. Você apenas tem que "saber". Pense em adicionar um como semelhante ao motivo pelo qual alguém pode, em um idioma do GC, adicionar uma dica a um coletor de lixo para prosseguir e executar um ciclo de coleta agora. Talvez você saiba que há uma tonelada de objetos prontos para serem limpos, você tem um loop que aloca vários objetos temporários, para que você "saiba" (ou a Instruments pode lhe dizer :) que adicionar um pool de liberação ao redor do loop seria um boa ideia.
Graham Perks
6
O exemplo de loop funciona perfeitamente sem liberação automática: cada objeto é desalocado quando a variável sai do escopo. A execução do código sem liberação automática exige uma quantidade constante de memória e mostra os ponteiros sendo reutilizados, e colocar um ponto de interrupção no desalocamento de um objeto mostra que ele é chamado uma vez a cada vez no loop, quando objc_storeStrong é chamado. Talvez o OSX faça algo idiota aqui, mas o autoreleasepool é completamente desnecessário no iOS.
Glenn Maynard
16

@autoreleasepoolnão libera automaticamente nada. Ele cria um pool de liberação automática, para que quando o final do bloco for atingido, quaisquer objetos que foram liberados automaticamente pelo ARC enquanto o bloco estava ativo receberão mensagens de liberação. O Guia de programação avançada de gerenciamento de memória da Apple explica assim:

No final do bloco do conjunto de liberação automática, os objetos que receberam uma mensagem de liberação automática dentro do bloco recebem uma mensagem de liberação - um objeto recebe uma mensagem de liberação para cada vez que recebe uma mensagem de liberação automática dentro do bloco.

fora
fonte
1
Não necessariamente. O objeto receberá uma releasemensagem, mas se a contagem de retenção for> 1, o objeto NÃO será desalocado.
precisa saber é
@andybons: atualizado; obrigado. Isso é uma alteração do comportamento pré-ARC?
Out
Isto está incorreto. Os objetos liberados pelo ARC receberão mensagens de liberação assim que forem liberados pelo ARC, com ou sem um pool de liberação automática.
Glenn Maynard
7

As pessoas geralmente entendem mal o ARC para algum tipo de coleta de lixo ou algo semelhante. A verdade é que, depois de algum tempo, as pessoas da Apple (graças aos projetos llvm e clang) perceberam que a administração de memória do Objective-C (todos os retainse releasesetc.) pode ser totalmente automatizada em tempo de compilação . Isto é, apenas lendo o código, mesmo antes de ser executado! :)

Para fazer isso, existe apenas uma condição: DEVEMOS seguir as regras , caso contrário, o compilador não poderá automatizar o processo no momento da compilação. Assim, para garantir que nós não quebrar as regras, não é permitido explicitamente escrita release, retainetc. Essas chamadas são injetados automaticamente em nosso código pelo compilador. Por isso internamente ainda temos autoreleases, retain, release, etc. É apenas que não precisamos de escrevê-los mais.

O A do ARC é automático no tempo de compilação, o que é muito melhor do que no tempo de execução, como a coleta de lixo.

Ainda o temos, @autoreleasepool{...}porque o fato de não infringir nenhuma das regras, somos livres para criar / drenar nossa piscina sempre que precisarmos :).

nacho4d
fonte
1
O ARC é um GC de contagem de referência, não um GC de marcação e varredura, como no JavaScript e Java, mas é definitivamente uma coleta de lixo. Isso não aborda a questão - "você pode" não responde à pergunta "por que deveria". Você não deveria.
Glenn Maynard
3

É porque você ainda precisa fornecer ao compilador dicas sobre quando é seguro que objetos liberados automaticamente saiam do escopo.

DougW
fonte
Você pode me dar um exemplo de quando você precisaria fazer isso?
MK12
Usando Pools de
Autorelease
Portanto, antes do ARC, por exemplo, eu tinha um CVDisplayLink em execução em um encadeamento secundário para meu aplicativo OpenGL, mas não criei um pool de autorelease em seu runloop porque sabia que não estava liberando automaticamente nada (ou usando bibliotecas que o fazem). Isso significa que agora eu preciso adicionar @autoreleasepoolporque não sei se o ARC pode decidir liberar algo automaticamente?
Mk12
@ Mk12 - Não. Você sempre terá um pool de liberação automática que será drenado toda vez que o loop principal for executado. Você só precisa adicionar um quando quiser garantir que os objetos liberados automaticamente sejam drenados antes do que seriam - por exemplo, na próxima vez em que executar o loop de execução.
precisa saber é o seguinte
2
@ DougW - Dei uma olhada no que o compilador está realmente fazendo e escrevi sobre o blog aqui - iphone.galloway.me.uk/2012/02/a-look-under-arcs-hood- –-episode-3 /. Espero que explique o que está acontecendo no tempo de compilação e no tempo de execução.
18720 Mathews #
2

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

Blocos e segmentos de pool de liberação automática

Cada encadeamento em um aplicativo Cocoa mantém sua própria pilha de blocos de conjuntos de liberação automática. Se você estiver gravando um programa somente para Fundação ou se desanexar um encadeamento, precisará criar seu próprio bloco de pool de liberação automática.

Se seu aplicativo ou encadeamento tiver vida longa e potencialmente gerar muitos objetos liberados automaticamente, você deverá usar blocos de conjuntos de lançamentos automáticos (como o AppKit e o UIKit fazem no encadeamento principal); caso contrário, os objetos liberados automaticamente se acumulam e sua pegada na memória aumenta. Se o seu segmento desanexado não fizer chamadas de cacau, não será necessário usar um bloco de pool de liberação automática.

Nota: Se você criar encadeamentos secundários usando as APIs de encadeamento POSIX em vez de NSThread, não poderá usar o Cocoa, a menos que o Cocoa esteja no modo multithreading. O cacau entra no modo multithreading somente após desanexar seu primeiro objeto NSThread. Para usar o Cocoa em threads POSIX secundários, seu aplicativo deve primeiro desanexar pelo menos um objeto NSThread, que pode sair imediatamente. Você pode testar se o cacau está no modo multithreading com o método da classe NSThread isMultiThreaded.

...

Na contagem automática de referência, ou ARC, o sistema usa o mesmo sistema de contagem de referência que o MRR, mas insere as chamadas apropriadas do método de gerenciamento de memória para você em tempo de compilação. Você é fortemente encorajado a usar o ARC para novos projetos. Se você usa o ARC, normalmente não há necessidade de entender a implementação subjacente descrita neste documento, embora em algumas situações possa ser útil. Para saber mais sobre o ARC, consulte Transição para o ARC Release Notes.

Raunak
fonte
2

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 autoreleasede 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 tempObjectForDatapossa 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 DispatchQueueou 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.

Mecki
fonte
-4

Parece haver muita confusão sobre esse tópico (e pelo menos 80 pessoas que provavelmente agora estão confusas sobre isso e acham que precisam espalhar o @autoreleasepool em torno de seu código).

Se um projeto (incluindo suas dependências) usar exclusivamente o ARC, o @autoreleasepool nunca precisará ser usado e não fará nada útil. O ARC manipulará a liberação de objetos no momento correto. Por exemplo:

@interface Testing: NSObject
+ (void) test;
@end

@implementation Testing
- (void) dealloc { NSLog(@"dealloc"); }

+ (void) test
{
    while(true) NSLog(@"p = %p", [Testing new]);
}
@end

exibe:

p = 0x17696f80
dealloc
p = 0x17570a90
dealloc

Cada objeto de teste é desalocado assim que o valor fica fora do escopo, sem aguardar a saída de um conjunto de liberação automática. (O mesmo ocorre com o exemplo NSNumber; isso apenas nos permite observar o desdobramento.) O ARC não usa o autorelease.

A razão pela qual @autoreleasepool ainda é permitida é para projetos ARC e não-ARC mistos, que ainda não fizeram a transição completa para o ARC.

Se você chamar um código não-ARC, ele poderá retornar um objeto liberado automaticamente. Nesse caso, o loop acima vazaria, pois o pool de liberação automática atual nunca será encerrado. É aí que você deseja colocar um @autoreleasepool em torno do bloco de código.

Mas se você fez completamente a transição do ARC, esqueça o pool de liberação automática.

Glenn Maynard
fonte
4
Esta resposta está errada e também vai contra a documentação do ARC. suas evidências são anedóticas, porque você está usando um método de alocação que o compilador decide não liberar automaticamente. Você pode facilmente ver isso não funcionando se você criar um novo inicializador estático para sua classe personalizada. Criar esta initializer e usá-lo em seu loop: + (Testing *) testing { return [Testing new] }. Então você verá que o acordo não será chamado até mais tarde. Isso é corrigido se você envolver o interior do loop em um @autoreleasepoolbloco.
Dima 14/07
@Dima Tentando iOS10, o dealloc é chamado imediatamente após a impressão do endereço do objeto. + (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
KudoCC 26/10/16
@KudoCC - Eu também vi o mesmo comportamento que você. Mas, quando joguei [UIImage imageWithData]a equação, de repente, comecei a ver o autoreleasecomportamento tradicional , exigindo @autoreleasepoolmanter o pico de memória em algum nível razoável.
26416 Rob
@ Rob Eu não consigo me ajudar a adicionar o link .
KudoCC 27/10/16