iphone toronto Entendendo a contagem de referência com cacau e objetivo-C




iphone xr screen size (12)

Estou apenas começando a dar uma olhada no Objective-C e no Cocoa com o objetivo de jogar com o iPhone SDK. Estou razoavelmente confortável com o conceito malloc e free C, mas o esquema de contagem de referências de Cocoa me deixa bastante confuso. Me disseram que é muito elegante quando você entende, mas eu ainda não acabei.

Como release , retain e release trabalho e quais são as convenções sobre seu uso?

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


Objective-C usa Reference Counting , o que significa que cada Object tem uma contagem de referência. Quando um objeto é criado, ele tem uma contagem de referência de "1". Simplesmente falando, quando um objeto é referido (ou seja, armazenado em algum lugar), ele fica "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 é diminuída em um.

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

Para alguns idiomas, as referências são automaticamente aumentadas e diminuídas, mas o objective-c não é uma dessas linguagens. 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-se de liberar quaisquer recursos adquiridos dentro do código é tedioso e propenso a erros. Objective-C introduz outro conceito destinado a tornar isso muito mais fácil: Autorelease Pools. Os pools 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 "autorelease" enviada para ele, o objeto procurará por qualquer pool de autorelease na pilha do thread atual. Ele adicionará o objeto à lista como um objeto para enviar uma mensagem "release" em algum momento no futuro, que geralmente é quando o próprio pool é liberado.

Tomando o código acima, você pode reescrevê-lo para ser mais curto e mais 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 é porque sabemos que alguns grupos de autorelease farão isso por nós mais tarde.

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




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

Se você alloc/init um objeto pessoalmente, ele vem com uma contagem de referência igual a 1. Você é responsável por limpá-lo quando não for mais necessário, seja chamando [foo release] ou [foo autorelease] . release limpa imediatamente, enquanto autorelease adiciona o objeto ao pool de autorelease, que será liberado automaticamente em um momento posterior.

Autorelease é principalmente para quando você tem um método que precisa retornar o objeto em questão ( assim você não pode liberá-lo manualmente, senão você estará retornando um objeto nulo ), mas você não quer segurá-lo, seja .

Se você adquirir um objeto no qual você não chamou alloc / init para obtê-lo - por exemplo:

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

mas você quer se agarrar a este objeto, você precisa chamar [foo reter]. Caso contrário, é possível que ele seja stringWithString e você estará segurando uma referência nula (como no exemplo stringWithString acima ). Quando você não precisar mais, chame [foo release] .


Não vou acrescentar nada ao específico de reter / liberar, a não ser que você queira pensar em gastar US $ 50 e obter o livro da Hillegass, mas sugiro fortemente começar a usar as ferramentas de instrumentos muito cedo no desenvolvimento de sua aplicação (até mesmo primeiro!). Para fazer isso, execute> Iniciar com ferramentas de desempenho. Eu começaria com Leaks, que é apenas um dos muitos instrumentos disponíveis, mas ajudará a mostrar quando você se esqueceu de liberar. É muito difícil saber com quanta informação você será apresentado. Mas confira este tutorial para se levantar e ir rápido:
TUTORIAL DE CACAU: FIXANDO FUGAS DE MEMÓRIA COM INSTRUMENTOS

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


Joshua (# 6591) - O material da coleção Garbage no Mac OS X 10.5 parece muito legal, mas não está disponível para o iPhone (ou se você quiser que seu aplicativo seja executado nas versões anteriores a 10.5 do Mac OS X).

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


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

  1. Se uma função que retorna um objeto tem alloc , create ou copy em seu nome, então o objeto é seu. Você deve chamar [object release] quando terminar de usá-lo. Ou CFRelease(object) , se for um objeto de Core-Foundation.

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

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

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


Como várias pessoas já mencionaram, o developer.apple.com/mac/library/documentation/Cocoa/Conceptual/… da Apple é de longe o melhor lugar para começar.

Um link útil que eu não vi mencionado ainda é o developer.apple.com/mac/library/documentation/Cocoa/Conceptual/… . Você vai encontrá-lo no meio dos documentos da Apple, se você lê-los, mas vale a pena ligar direto. É um resumo executivo brilhante das regras de gerenciamento de memória com exemplos e erros comuns (basicamente o que outras respostas estão tentando explicar, mas não tão bem).



As respostas acima dão explicações claras sobre o que a documentação diz; o problema em que as pessoas mais novas se deparam são os casos não documentados. Por exemplo:

  • Autorelease : os docs dizem que isso desencadeará um lançamento "em algum momento no futuro". QUANDO?! Basicamente, você pode contar com o objeto em volta 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 evento atual. (Eu acho que Matt disse isso antes)

  • Strings estáticas : NSString *foo = @"bar"; - você tem que 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ê a criou, você a possui e espera-se liberá-la.

Em geral, a maneira como os novos programadores do Cocoa são confundidos é não entender quais rotinas retornam um objeto com um 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 de NSString) são considerados autoreleased.
  • Implemente um método -dealloc para liberar as variáveis ​​de instância que você possui

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

A segunda bala diz: se você usar um construtor de conveniência e precisar que o objeto permaneça por perto (como acontece com uma imagem a ser desenhada posteriormente), será necessário retê-lo (e depois liberá-lo mais tarde).

O 3º deve ser autoexplicativo.


Vamos começar com retain e release ; autorelease é realmente apenas um caso especial, uma vez que você entenda os conceitos básicos.

No Cocoa, cada objeto registra quantas vezes ele está sendo referenciado (especificamente, a classe base NSObject implementa isso). Ao chamar retain um objeto, você está dizendo que deseja aumentar sua contagem de referência em um. Ao chamar release , você diz ao objeto que está release e sua contagem de referência é diminuída. Se, depois de chamar a release , a contagem de referência for agora zero, a memória desse objeto será liberada pelo sistema.

A maneira básica como isso difere do malloc e do free é que qualquer objeto não precisa se preocupar com outras partes do sistema, porque você liberou a memória que estava usando. Supondo que todos estejam jogando e retendo / liberando de acordo com as regras, quando uma parte do código retém e depois libera o objeto, qualquer outra parte do código que faça referência ao objeto não será afetado.

O que às vezes pode ser confuso é saber as circunstâncias sob as quais você deve ligar para retain e release . Minha regra geral é que se eu quiser me agarrar a um objeto por algum tempo (se for uma variável de membro em uma classe, por exemplo), então eu preciso ter certeza de que a contagem de referência do objeto me conhece. Conforme descrito acima, a contagem de referência de um objeto é incrementada chamando retain . Por convenção, também é incrementado (definido como 1, realmente) quando o objeto é criado com um método "init". Em qualquer um desses casos, é minha responsabilidade chamar release no objeto quando eu terminar com ele. Se eu não fizer isso, 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 . Autorelease é usado como uma maneira conveniente (e às vezes necessária) de dizer ao sistema para liberar esse objeto depois de um tempo. Do ponto de vista do encanamento, quando o autorelease é chamado, o NSAutoreleasePool do encadeamento atual é alertado sobre a chamada. O NSAutoreleasePool agora sabe que, uma vez que obtém uma oportunidade (após a iteração atual do loop de eventos), ele pode chamar release no objeto. De nossa perspectiva como programadores, ele cuida de chamar release para nós, então não precisamos (e, na verdade, não deveríamos).

O que é importante notar é que (novamente, por convenção) todos os métodos de classe de criação de objeto retornam um objeto autoreleased. Por exemplo, no exemplo a seguir, a variável "s" tem uma contagem de referência de 1, mas depois que o loop de evento for concluído, ele será destruído.

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

Se você quiser se agarrar a essa string, precisará chamar retain explicitamente e depois release la explicitamente quando terminar.

Considere o seguinte código de código (muito artificial) e você verá uma situação em que o 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];
}

Eu percebo que tudo isso é um pouco confuso - em algum momento, porém, ele vai clicar. Aqui estão algumas referências para você ir:

  • Introdução da Apple ao gerenciamento de memória.
  • Cacau Programação para Mac OS X (4ª Edição) , por Aaron Hillegas - um livro muito bem escrito com muitos grandes exemplos. Parece um tutorial.
  • Se você está realmente mergulhando, você pode ir ao Big Nerd Ranch . Este é um centro de treinamento dirigido por Aaron Hillegas - o autor do livro mencionado acima. Eu participei do curso Intro to Cocoa há alguns anos atrás, e foi uma ótima maneira de aprender.





memory