Entendendo a contagem de referência com cacau e Objective-C


122

Estou apenas começando a dar uma olhada no Objective-C e no Cocoa para brincar com o iPhone SDK. Estou razoavelmente confortável com C malloce freeconceito, mas o esquema de contagem de referências de Cocoa me deixa um pouco confuso. Disseram-me que é muito elegante depois que você entende, mas ainda não superei.

Como fazer release, retaine autoreleasetrabalho e quais são as convenções sobre seu uso?

(Ou, na sua falta, o que você leu e o ajudou a obtê-lo?)

Respostas:


148

Vamos começar com retaine release; autoreleaseé realmente apenas um caso especial quando você entende os conceitos básicos.

No Cocoa, cada objeto controla quantas vezes está sendo referenciado (especificamente, a NSObjectclasse base implementa isso). Ao chamar retainum objeto, você está dizendo que deseja aumentar a contagem de referências em um. Ao ligar release, você diz ao objeto que o está soltando e sua contagem de referência é diminuída. Se, após a chamada release, a contagem de referência agora for zero, a memória desse objeto será liberada pelo sistema.

A maneira básica pela qual isso difere malloce freeé que um determinado objeto não precisa se preocupar com o travamento de outras partes do sistema, porque você liberou a memória que eles estavam usando. Supondo que todos estejam jogando junto e mantendo / liberando de acordo com as regras, quando um trecho de código retém e depois libera o objeto, qualquer outro trecho de código que também faça referência ao objeto não será afetado.

O que às vezes pode ser confuso é conhecer as circunstâncias em que você deve ligar retaine release. Minha regra geral é que, se eu quiser me apegar a um objeto por algum tempo (se for uma variável de membro de uma classe, por exemplo), preciso garantir que a contagem de referência do objeto saiba de mim. Como descrito acima, a contagem de referência de um objeto é incrementada chamando retain. Por convenção, também é incrementado (realmente definido como 1) quando o objeto é criado com um método "init". Em qualquer um desses casos, é minha responsabilidade chamar releaseo objeto quando terminar. Caso contrário, haverá um vazamento de memória.

Exemplo de criação de objeto:

NSString* s = [[NSString alloc] init];  // Ref count is 1
[s retain];                             // Ref count is 2 - silly
                                        //   to do this after init
[s release];                            // Ref count is back to 1
[s release];                            // Ref count is 0, object is freed

Agora para autorelease. A liberação automática é usada como uma maneira conveniente (e às vezes necessária) de instruir o sistema a liberar esse objeto depois de um tempo. Do ponto de vista do encanamento, quando autoreleaseé chamado, o encadeamento atual NSAutoreleasePoolé alertado sobre a chamada. O NSAutoreleasePoolagora sabe que, quando obtiver uma oportunidade (após a iteração atual do loop de eventos), poderá chamar releaseo objeto. Do nosso ponto de vista como programadores, ele cuida de releasenos chamar , para que não precisemos (e de fato não deveríamos).

O que é importante observar é que (novamente, por convenção) todos os métodos de classe de criação de objeto retornam um objeto liberado automaticamente. Por exemplo, no exemplo a seguir, a variável "s" tem uma contagem de referência 1, mas após a conclusão do loop de eventos, ela será destruída.

NSString* s = [NSString stringWithString:@"Hello World"];

Se você deseja pendurar nessa sequência, precisará chamar retainexplicitamente e, em seguida, explicitamente releasequando terminar.

Considere o seguinte código (muito elaborado) de código e você verá uma situação em que autoreleaseé necessário:

- (NSString*)createHelloWorldString
{
    NSString* s = [[NSString alloc] initWithString:@"Hello World"];

    // Now what?  We want to return s, but we've upped its reference count.
    // The caller shouldn't be responsible for releasing it, since we're the
    // ones that created it.  If we call release, however, the reference 
    // count will hit zero and bad memory will be returned to the caller.  
    // The answer is to call autorelease before returning the string.  By 
    // explicitly calling autorelease, we pass the responsibility for
    // releasing the string on to the thread's NSAutoreleasePool, which will
    // happen at some later time.  The consequence is that the returned string 
    // will still be valid for the caller of this function.
    return [s autorelease];
}

Sei que tudo isso é um pouco confuso - em algum momento, porém, ele clicará. Aqui estão algumas referências para você começar:

  • Introdução da Apple ao gerenciamento de memória.
  • Cocoa Programming for Mac OS X (4ª Edição) , de Aaron Hillegas - um livro muito bem escrito, com muitos ótimos exemplos. Lê como um tutorial.
  • Se você está realmente mergulhando, pode ir para o Big Nerd Ranch . Esta é uma instalação de treinamento administrada por Aaron Hillegas - o autor do livro mencionado acima. Eu participei do curso Introdução ao cacau lá há vários anos e foi uma ótima maneira de aprender.

8
Você escreveu: "Ao chamar o autorelease, aumentamos temporariamente a contagem de referência". Acho que isso está errado; autorelease marca apenas o objeto a ser lançado no futuro, não aumenta a contagem de ref: cocoadev.com/index.pl?AutoRelease
LKM

2
"Agora para liberação automática. A liberação automática é usada como uma maneira conveniente (e às vezes necessária) de instruir o sistema a liberar esse objeto depois de um tempo". Como sentença inicial, isso está errado. Não diz ao sistema para "liberá-lo", mas para diminuir a contagem de retenção.
mmalc 20/10/08

3
Muito obrigado pela boa explicação. Apenas uma coisa que ainda não está clara. Se NSString* s = [[NSString alloc] initWithString:@"Hello World"];retorna um objeto liberado automaticamente (como você o escreve), por que eu tenho que fazer um return [s autorelease];e defini-lo como "autorelease" novamente e não apenas return s?
znq 5/05

3
@ Stefan: [[NSString alloc] initWithString:@"Hello World"]NÃO retornará um objeto liberado automaticamente . Sempre que allocé chamado, a contagem de referência é definida como 1 e é de responsabilidade desse código garantir que ele seja liberado. A [NSString stringWithString:]chamada, por outro lado, não retornar um objeto autoreleased.
Matt Dillard 5/05

6
Curiosidades divertidas: Como a resposta usa @ "" e NSString, as seqüências de caracteres são constantes e, portanto, a contagem absoluta de retenção será constante e totalmente irrelevante ... não torna a resposta errada, por qualquer meio, apenas reforça o fato de que contagens absolutas de retenção nunca são realmente algo com que você deva se preocupar.
bbum

10

Se você entende o processo de retenção / liberação, existem duas regras de ouro que são "duh" óbvias para os programadores estabelecidos do Cocoa, mas infelizmente raramente são explicadas claramente para os novatos.

  1. Se uma função que retorna um objeto possui alloc, createou copyem seu nome, o objeto é seu. Você deve ligar [object release]quando terminar. Ou CFRelease(object), se for um objeto Core-Foundation.

  2. Se NÃO tiver uma dessas palavras em seu nome, o objeto pertence a outra pessoa. Você deve ligar [object retain]se desejar manter o objeto após o término de sua função.

Você estaria bem servido também para seguir esta convenção nas funções que você mesmo criar.

(Nitpickers: Sim, infelizmente existem algumas chamadas de API que são exceções a essas regras, mas são raras).


11
Isso é incompleto e impreciso. Eu continuo a não entender por que as pessoas tentar repetir as regras, em vez de simplesmente apontando para a documentação pertinente: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/...
mmalc

4
As regras da Core Foundation, em particular, são diferentes das do cacau; consulte developer.apple.com/documentation/CoreFoundation/Conceptual/…
mmalc 20/10/2008

1
Eu também discordo. Se uma função está retornando algo que não deseja possuir, deve liberá-la automaticamente. É o chamador do trabalho de funções para retê-lo (se desejado). Não deve ter nada a ver com o nome de qualquer método que está sendo chamado. Isso é mais codificação no estilo C, onde a propriedade dos objetos não é clara.
Sam

1
Desculpe! Eu acho que fui apressado em votar. Regras de gerenciamento de memória Sua resposta quase cita o documento da apple.
Sam

8

Se você está escrevendo um código para a área de trabalho e pode segmentar o Mac OS X 10.5, deve pelo menos examinar a coleta de lixo do Objective-C. Ele realmente simplificará a maior parte do seu desenvolvimento - é por isso que a Apple se esforça ao máximo para criá-lo e fazê-lo funcionar bem.

Quanto às regras de gerenciamento de memória quando não estiver usando o GC:

  • Se você criar um novo objeto usando +alloc/+allocWithZone:, +new, -copyou -mutableCopyou se -retainum objeto, você está tomando posse dela e deve garantir que ele é enviado -release.
  • Se você receber um objeto de qualquer outra forma, você não o proprietário do mesmo e deve não garantir que ele é enviado -release.
  • Se você deseja garantir que um objeto seja enviado, -releasevocê mesmo pode enviá-lo ou o objeto -autoreleasee o pool de liberação automática atual o enviará -release(uma vez por recebido -autorelease) quando o pool for drenado.

Normalmente, -autoreleaseé usado como uma maneira de garantir que os objetos permaneçam durante todo o evento atual, mas sejam limpos posteriormente, pois há um pool de liberação automática que envolve o processamento de eventos do Cocoa. No cacau, é muito mais comum retornar objetos a um chamador que é liberado automaticamente do que retornar objetos que o próprio chamador precisa liberar.


6

O Objective-C usa a Contagem de referência , o que significa que cada Objeto tem uma contagem de referência. Quando um objeto é criado, ele tem uma contagem de referência "1". Simplesmente falando, quando um objeto é referido (ou seja, armazenado em algum lugar), ele é "retido", o que significa que sua contagem de referência é aumentada em um. Quando um objeto não é mais necessário, ele é "liberado", o que significa que sua contagem de referência é reduzida em um.

Quando a contagem de referência de um objeto é 0, o objeto é liberado. Esta é a contagem básica de referência.

Para alguns idiomas, as referências são automaticamente aumentadas e diminuídas, mas o objetivo-c não é um desses idiomas. Assim, o programador é responsável por reter e liberar.

Uma maneira típica de escrever um método é:

id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;

O problema de precisar lembrar de liberar quaisquer recursos adquiridos dentro do código é tedioso e propenso a erros. O Objective-C apresenta outro conceito que visa facilitar isso: pools de liberação automática. Conjuntos de liberação automática são objetos especiais instalados em cada encadeamento. Eles são uma classe bastante simples, se você procurar NSAutoreleasePool.

Quando um objeto recebe uma mensagem de "autorelease" enviada a ele, o objeto procurará por todos os pools de autorelease na pilha para esse thread atual. Ele adicionará o objeto à lista como um objeto para o qual enviará uma mensagem de "release" em algum momento no futuro, que geralmente ocorre quando o próprio pool é liberado.

Tomando o código acima, você pode reescrevê-lo para ser mais curto e fácil de ler, dizendo:

id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;

Como o objeto é liberado automaticamente, não precisamos mais chamar explicitamente "release". Isso ocorre porque sabemos que algum pool de autorelease fará isso por nós mais tarde.

Espero que isso ajude. O artigo da Wikipedia é muito bom sobre a contagem de referências. Mais informações sobre pools de autorelease podem ser encontradas aqui . Observe também que, se você estiver construindo para o Mac OS X 10.5 e posterior, pode pedir ao Xcode para construir com a coleta de lixo ativada, permitindo ignorar completamente reter / liberar / liberação automática.


2
Isso está errado. Não há necessidade de enviar alguma liberação de objeto ou autorlease em nenhum dos exemplos mostrados.
Mmalc

6

Joshua (# 6591) - O material de coleta de lixo no Mac OS X 10.5 parece bem legal, mas não está disponível para o iPhone (ou se você deseja que o aplicativo seja executado nas versões anteriores ao 10.5 do Mac OS X).

Além disso, se você estiver escrevendo uma biblioteca ou algo que possa ser reutilizado, o uso do modo GC impede que qualquer pessoa que use o código também use o modo GC, pelo que entendi, qualquer pessoa que tente escrever código amplamente reutilizável tende a gerenciar memória manualmente.


2
É perfeitamente possível escrever uma estrutura híbrida que suporte tanto o GC quanto a contagem de referência.
mmalc

6

Como sempre, quando as pessoas começam a tentar reformular o material de referência, quase invariavelmente entendem algo errado ou fornecem uma descrição incompleta.

A Apple fornece uma descrição completa do sistema de gerenciamento de memória do Cocoa no Memory Management Programming Guide for Cocoa , no final do qual existe um resumo breve, porém preciso, das Regras de gerenciamento de memória .




2
Na verdade, este é um resumo muito melhor de uma página: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…
Brian Moeskau

6

Não adicionarei detalhes específicos de retenção / liberação, a menos que você queira gastar US $ 50 e obter o livro Hillegass, mas eu sugiro enfaticamente que você use as ferramentas Instruments muito cedo no desenvolvimento de seu aplicativo (até mesmo o seu primeiro!). Para fazer isso, execute-> Iniciar com ferramentas de desempenho. Eu começaria com o Leaks, que é apenas um dos muitos instrumentos disponíveis, mas ajudará a mostrar quando você se esquecer de lançar. É assustador quanta informação você será apresentado. Mas confira este tutorial para acelerar e acelerar:
TUTORIAL DE CACAU: CONSERVANDO FUGAS DE MEMÓRIA COM INSTRUMENTOS

Na verdade, tentar forçar vazamentos pode ser uma maneira melhor de aprender a evitá-los! Boa sorte ;)


5

Matt Dillard escreveu :

return [[s autorelease] release];

A liberação automática não retém o objeto. O lançamento automático simplesmente o coloca na fila para ser lançado posteriormente. Você não deseja ter uma declaração de lançamento lá.




4

A resposta do NilObject é um bom começo. Aqui estão algumas informações adicionais referentes ao gerenciamento manual de memória ( necessário no iPhone ).

Se você pessoalmente alloc/initum objeto, ele vem com uma contagem de referência de 1. Você é responsável por limpá-lo quando ele não for mais necessário, ligando [foo release]ou [foo autorelease]. A liberação o limpa imediatamente, enquanto a liberação automática adiciona o objeto ao pool de liberação automática, que a liberará automaticamente posteriormente.

autorelease é principalmente para quando você tem um método que precisa retornar o objeto em questão ( para que você não possa liberá-lo manualmente, caso contrário, retornará um objeto nulo ), mas também não deseja segurá-lo. .

Se você adquirir um objeto em que não chamou aloc / init para obtê-lo - por exemplo:

foo = [NSString stringWithString:@"hello"];

mas você quer se apegar a esse objeto, precisa chamar [foo reter]. Caso contrário, é possível que ele aconteça autoreleasede você manterá uma referência nula (como seria no stringWithStringexemplo acima ). Quando você não precisar mais dele, ligue [foo release].


2

As respostas acima fornecem reformulações claras do que a documentação diz; o problema em que a maioria das novas pessoas se depara são os casos não documentados. Por exemplo:

  • Autorelease : os documentos dizem que o lançamento será "em algum momento no futuro". QUANDO?! Basicamente, você pode contar com o objeto existente até sair do código de volta ao loop de eventos do sistema. O sistema pode liberar o objeto a qualquer momento após o ciclo de eventos atual. (Acho que Matt disse isso antes.)

  • Cordas estáticas : NSString *foo = @"bar";- você precisa reter ou liberar isso? Não. Que tal

    -(void)getBar {
        return @"bar";
    }

    ...

    NSString *foo = [self getBar]; // still no need to retain or release
  • A regra de criação : se você o criou, é o proprietário e espera-se lançá-lo.

Em geral, a maneira como os novos programadores do Cocoa são confusos é não entender quais rotinas retornam um objeto com a retainCount > 0.

Aqui está um trecho de Regras muito simples para gerenciamento de memória no cacau :

Regras de contagem de retenção

  • Dentro de um determinado bloco, o uso de -copy, -alloc e -retain deve ser igual ao uso de -release e -autorelease.
  • Objetos criados usando construtores de conveniência (por exemplo, stringWithString do NSString) são considerados liberados automaticamente.
  • Implemente um método -dealloc para liberar as variáveis ​​instáveis ​​que você possui

O primeiro marcador diz: se você ligou alloc(ou new fooCopy), precisa chamar release nesse objeto.

O segundo marcador diz: se você usa um construtor de conveniência e precisa que o objeto fique por perto (como em uma imagem a ser desenhada posteriormente), é necessário retê-lo (e depois liberá-lo).

O terceiro deve ser auto-explicativo.


"Autorelease: os documentos dizem que a versão será acionada" em algum momento no futuro. "QUANDO ?!" Os documentos são claros quanto a esse ponto: "liberação automática significa apenas" enviar uma mensagem de liberação posteriormente "(para alguma definição de posterior - consulte" Pools de liberação automática ")." Exacely quando depende da pilha piscina disparo automático ...
mmalc

... "O sistema PODE liberar o objeto a qualquer momento após o ciclo do evento atual." Isso faz com que o som do sistema um pouco menos determinista do que é ...
mmalc

... NSString foo = [auto getBar]; // ainda não há necessidade de reter ou liberar Isso está errado. Quem chama getBar não conhece os detalhes da implementação; portanto, deve reter / liberar (normalmente via acessadores) se quiser usá-lo fora do escopo atual.
mmalc 02/12/08

O artigo "Regras muito simples para gerenciamento de memória no cacau" está desatualizado em vários aspectos - em particular "Objetos criados usando construtores de conveniência (por exemplo, stringWithString do NSString) são considerados liberados automaticamente". não está certo - simplesmente "não pertence ao destinatário".
mmalc 02/12/08


0

Como várias pessoas já mencionaram, a introdução ao gerenciamento de memória da Apple é de longe o melhor lugar para começar.

Um link útil que ainda não vi mencionado é o Gerenciamento prático de memória . Você o encontrará no meio dos documentos da Apple, se os ler, mas vale a pena vincular diretamente. É um resumo executivo brilhante das regras de gerenciamento de memória com exemplos e erros comuns (basicamente o que outras respostas aqui estão tentando explicar, mas não tão bem).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.