terça-feira, 27 de novembro de 2012

O Gerenciamento de Memória Manual no Objective-C

Olá a todos!!!

Toda essa introdução e cronologia que apresentei foram exclusivamente realizadas para que entendamos o assunto mais macabro referente à linguagem Objective-C: Seu gerenciamento de memória.

A evolução do gerenciamento de memória no Objective-C acompanha os avanços das Frameworks e plataformas da Apple. Sabemos que quando estamos trabalhando nessa linguagem, podemos chegar a ter de trabalhar com o gerenciamento de memória singular das linguagens C e C++ de bibliotecas que venhamos a utilizar. Portanto são 3 linguagens não gerenciadas que devemos tratar de maneira correta para não termos problemas de Memory Leak ou Dangling Pointers no código.

As versões anteriores do Mac OS X 10.5 possuíam gerenciamento de memória manual assim como no C e C++. Essa realidade também se tornou constante nas plataformas IOS até a versão 5.

No caso do Objective-C, todas as classes devem, direta ou indiretamente, herdar de NSObject da Foundation para ter acesso aos métodos de gerência manual de memória. O que acontece na prática, é que cada objeto criado na memória que é filho de NSObject incrementa um contador denominado de Reference Count/Retain Count.

Para alocarmos um objeto na memória, usamos os métodos alloc e copy. Ambos criam o objeto, mas o segundo serve especialmente para criarmos uma cópia de um outro objeto. Da mesma forma, para desalocarmos, chamamos o método release do objeto em questão.

Na prática, alloc e copy incrementam o contador, e release decrementa. Quando o contador chegar a zero, o objeto se torna imediatamente inválido (isso não significa que será imediatamente desalocado, mas mesmo assim perdemos o acesso ao mesmo).

Se você entendeu até aqui, podemos ir para o passo 2 e mais importante com relação ao gerenciamento de memória manual no Objective-C. A fim de gerar boas práticas de programação na linguagem e facilitar o desenvolvimento, criaram-se convenções que estabelecem que:
  1. Somos responsáveis por chamar o método release dos objetos que criamos com os operadores: alloc e copy já mencionados, e os operadores new (responsável por chamar o alloc e o "construtor default" do objeto num comando só) e mutableCopy.
  2. Não somos responsáveis por desalocar da memória os objetos que criamos por meio de Factory Methods que, no Objective-C, são chamados de Convenience Constructors. Um exemplo de tais métodos são os da classe NSString que nos retornam uma NSString alocada e não necessitamos fazer chamada a alloc, copy, new ou mutableCopy.
Um guia completo sobre gerenciamento de memória manual pode ser encontrado aqui. A documentação da Apple é um guia excelente que deve ser usado abusivamente.

Até aqui, entendemos que devemos chamar release dos objetos que criamos usando os métodos já referidos. Isso significa dizer que quando temos a POSSE DO OBJETO devemos desalocá-lo.

Sendo assim, temos o caso em que podemos receber um objeto por referência e desejarmos manter a referência sobre o objeto ORIGINAL na classe, ou seja, sem utilizar copy. Para tal, chamamos o método retain do objeto. Esse irá incrementar em 1 a referência do objeto em questão (o que não ocorreria com copy, aonde criamos uma nova instância e, portanto, um novo retain count).

E no caso em que criamos os nossos próprios Convenience Constructors? Se somos responsáveis pela remoção do objeto da memória nesse caso, como a faremos?

Esse é o ponto em questão que devemos nos focar para entender de vez o gerenciamento de memória manual. Para gerar o release dos objetos criados em um Convenience Constructor, chamamos antes de retornar o objeto no método (ou seja, antes do fim do escopo) o método autorelease. Basicamente, este método dirá ao compilador que desejamos decrementar o retain count desse objeto mais tarde. Mas quando seria esse mais tarde?

Ai é que entram as AutoReleasePools. Toda a vez que o método autorelease for chamado, uma referência desse objeto será adicionada à ÚLTIMA AUTORELEASEPOOL CRIADA.

Vamos ver um exemplo em código:


Assim, todo código que estiver entre uma NSAutoreleasePool e [pool drain] que tiver um objeto chamando o método autorelease, uma referência desse objeto será adicionada à pilha. Quando o método drain for chamado, um release para cada referência dentro da pilha será chamado. Portanto, não há garantias que o objeto continue válido após o fim do escopo de uma NSAutoreleasePool.

É importante notar que no caso de NSAutoreleasePools jamais chamamos o método release!!! Devemos em vés disso chamar o método drain, que se assegurará de dar release nas referências de objetos e na pilha em questão.

Nas novas versões do Xcode (a partir da 4.2), com o novo compilador (LLVM 3.0), os projetos já suportam blocos de @autoreleasepool. Segundo a documentação, eles são mais rápidos e trabalham melhor com o novo modelo de gerenciamento de memória (o qual será comentado no próximo post). Ou seja, o código demonstrado abaixo realiza a mesma coisa que o anterior, mas é o novo padrão da Apple que deve ser seguido:


Um último tópico a ser destacado a respeito das @autoreleasepool é com relação ao momento em que se justifica a criação de uma pool. Observe a seguir os momentos em que devemos realizar tal ação:
  1. Se estivermos a desenvolver uma aplicação que não se baseia no Application Kit (o Application Kit instancia automaticamente uma pool no início de cada ciclo de eventos). Um caso desse tipo de aplicação são as criadas sobre o template Command Line Tool;
  2. Se um determinado ciclo de código gera muitos objetos temporários instanciados de um Convenience Constructor;
  3. Se criarmos uma thread secundária (cada thread deve possuir sua própria @autoreleasepool).

Ufaaa!!! Por hora é só. No próximo post, irei demonstrar as principais features da linguagem, concatenando com todos os conceitos aqui apresentados. Se você ainda não entendeu, não se preocupe. Esse é o ponto que irá te retirar no estágio larval em Objective-C para o estágio Newbie...

Até lá...

Nenhum comentário:

Postar um comentário

Obrigado por deixar a sua participação!