Swift e Objective-C

História do gerenciamento de memória do Objective-C: do MRC ao ARC

Quem trabalha com desenvolvimento para iOS provavelmente já se perguntou algo pelo menos uma vez.

5 min de leitura
Imagem de capa de História do gerenciamento de memória do Objective-C: do MRC ao ARC

Quem trabalha com desenvolvimento para iOS provavelmente já se perguntou algo pelo menos uma vez.

“Por que o código antigo de Objective-C está cheio de retain e release?”

Se você começou com Swift, talvez tenha ficado confuso ao abrir código antigo.

Hoje vamos revisar a história do gerenciamento de memória do Objective-C, do MRC ao ARC.

Primeiro, o ponto principal.

O gerenciamento de memória do Objective-C passou da era em que os desenvolvedores contavam manualmente (MRC) para a era em que o compilador fazia isso por eles (ARC).

A coleta de lixo chegou a ser testada brevemente, mas o ARC apareceu em 2011 e acabou definindo o caminho.

Vamos examinar cada era.


O que é contagem de referências? Conceitos básicos de gerenciamento de memória

Antes de entrar na história, vamos fixar um conceito.

A contagem de referências (reference count).

Pense em cada objeto como tendo um número que conta quantas pessoas o estão mantendo.

Se o número for pelo menos 1, ele continua vivo; quando chega a 0, é liberado da memória.

Por isso, você aumenta o número ao manter um objeto (retain) e o diminui ao liberá-lo (release).

Essa regra simples é a base do gerenciamento de memória do Objective-C.

O fluxo da contagem de referências que decide a vida ou a morte com um único número
O fluxo da contagem de referências que decide a vida ou a morte com um único número

A era do MRC: quando os desenvolvedores contavam manualmente

Antes do ARC, na era do MRC (Manual Retain Count), esse número era gerenciado manualmente.

Código antigo cheio de retain e release; é confuso à primeira vista
Código antigo cheio de retain e release; é confuso à primeira vista

As regras eram claras. Muitos as memorizavam usando o termo “NARC”.

  • Eu sou responsável pelos objetos criados com New, Alloc, Retain ou Copy
  • Objetos pelos quais sou responsável devem executar release quando terminam de ser usados

O código real era assim.

// Ao criar um objeto, a contagem de referências fica 1
NSObject *obj = [[NSObject alloc] init];
[obj retain];   // contagem 2
[obj release];  // contagem 1
[obj release];  // contagem 0 → liberar memória

O problema começava aqui.

Se você esquecesse release, ocorria um vazamento de memória; se liberasse cedo demais, tocar em um objeto que já havia desaparecido encerrava o app.

O famoso crash de “objeto zumbi”.

Era preciso continuar contando mentalmente quantas referências o objeto mantido tinha, uma tarefa realmente cansativa.


autorelease: “Vou liberar um pouco mais tarde, não agora”

A era do MRC tinha mais uma situação complicada.

Isso acontecia quando um método criava um objeto novo e o retornava para fora.

- (NSString *)greeting {
    NSString *msg = [[NSString alloc] initWithString:@"Olá"];
    return msg; // Eu alloc criei, então devo release fazer isso, mas quando?
}

Pelas regras, quem chama alloc precisa executar release.

Mas, se você fizer release antes do return, o objeto desaparece antes de o receptor poder usá-lo. Se não fizer, haverá vazamento.

autorelease surgiu para resolver esse dilema.

Ele agenda o release para “não agora, mas daqui a pouco”.

Um objeto autoreleased entra em uma lista de espera chamada autorelease pool e recebe release quando o pool é esvaziado, normalmente ao fim de uma volta do run loop.

Assim, o receptor podia receber e usar o objeto com segurança.

Construtores de conveniência como [NSString stringWithFormat:], que retornavam objetos sem alloc, usavam todos esse método.

Esse autorelease pool sobreviveu na era do ARC com o nome de bloco @autoreleasepool. É uma ferramenta prática para picos de memória em loops, por isso tratei do assunto em outro artigo.


Por que a coleta de lixo falhou?

A Apple também conhecia esse inconveniente.

Por isso, em 2007, o Mac OS X 10.5 Leopard introduziu a coleta de lixo (GC), que limpava a memória automaticamente como o Java.

Parecia conveniente, pois os desenvolvedores não precisavam mais escrever retain nem release.

Mas os resultados não foram bons.

Como o GC rodava em segundo plano, o app podia travar brevemente em momentos imprevisíveis. Além disso, era pesado para o iPhone, sensível a desempenho e bateria.

Por isso, ele nunca foi introduzido no iOS.

Por fim, o GC para Mac foi descontinuado (deprecated) a partir do OS X 10.8, em 2012.


A chegada do ARC: o compilador conta por você

Então, em 2011, a solução apresentada pela Apple foi o ARC (Automatic Reference Counting).

Ele foi lançado com o Xcode 4.2, o iOS 5 e o OS X 10.7 Lion.

A ideia do ARC era inteligente.

A contagem de referências continuava igual, mas o compilador inseria retain e release automaticamente no lugar do desenvolvedor.

Diferentemente do GC, que executa uma limpeza separada em tempo de execução, o ARC insere o código de liberação nos pontos necessários durante a compilação.

Com isso, o gerenciamento de memória foi automatizado com quase nenhum impacto no desempenho em tempo de execução.

A comparação entre as duas eras fica assim.

Categoria MRC ARC
Período de surgimento Início–2011 2011 (iOS 5)
retain/release Escrito manualmente Inserido automaticamente pelo compilador
autorelease Chamada direta Gerenciado pelo compilador
Risco de vazamento de memória Alto Muito reduzido
Impacto no desempenho Nenhum Quase nenhum

Mas o ARC também não é onipotente.

Como não consegue resolver referências cíclicas automaticamente, o desenvolvedor precisa quebrá-las com referências fracas como weak ou unowned.

Esse conceito continua exatamente igual no Swift que usamos hoje.


Perguntas frequentes (Q&A)

P. Ainda preciso aprender MRC?

Na prática, quase nunca é necessário escrever código novo com MRC. Ainda assim, bibliotecas antigas ou entrevistas podem cobrar o conceito, então vale conhecer os princípios.

P. autorelease também é usado com ARC?

Você não o chama diretamente, mas o mecanismo continua funcionando sob o ARC. Por isso, o bloco @autoreleasepool continua sendo útil.

P. O Swift também usa ARC?

Sim, o Swift também é baseado em ARC. Por isso, referências cíclicas e o conceito de weak são igualmente importantes no Swift.


Em resumo, o gerenciamento de memória do Objective-C evoluiu de “MRC contado à mão → breve experimento com GC → ARC contado pelo compilador”.

O gerenciamento automático de memória que hoje damos como certo foi aprimorado após inúmeros crashes e tentativas.

Espero que esse percurso seja um pequeno mapa para quem estuda desenvolvimento para iOS. Boa programação!


Referências

Continue lendo