vinibaggio.net

Properties

Observação: existe grande possibilidade de eu estar falando alguma merda, sou novo no Objective-C :-)

Gerenciamento de memória — o básico

Para alocar um objeto no Objective-C, é necessário basicamente chamar dois métodos: o alloc e o init. Em termos simples, o alloc é um método mais baixo nível e serve para alocar memória para o objeto (parecido com a família *alloc no C). O init e suas variações, correspondem ao construtor:

{{< highlight objc >}}

NSString* coolString = [[NSString alloc] init]; NSString* anotherCoolString = [[NSString alloc] initWithString:@“Cool string!”];

[coolString release]; [anotherCoolString release];

{{< / highlight >}}

O controle de memória feito internamente pelo Objective-C é simples. No momento que eu faço alloc, o número de referências para o objeto é incrementado. Quando faço release, esse número é reduzido. Quando o contador chega a zero, o objeto é liberado da memória:

Reference Counting Fonte da imagem: Cocoa Dev Central

O retain usamos para uma situação um pouco diferente — usamos quando queremos declaradamente aumentar a contagem de referência para um mesmo objeto em memória, por exemplo, quando queremos guardar aquela referência em um outro objeto.

Mas e o garbage collector?

No Objective-C, é possível usar um dos parâmetros na compilação:

  • -fobjc-gc-only: Há apenas Garbage Collector
  • -fobjc-gc: Suporta GC e retain/release

A questão é que eles não podem ser usados em aplicações iOS por enquanto, apenas no Snow Leopard.

Mais detalhes em Garbage Collection for Cocoa Essential

Getters e Setters

Vamos a um exemplo um pouco mais complexo, na nossa boa e velha classe Car:

Car.h {{< highlight objc >}}

import <Cocoa/Cocoa.h>

@interface Car : NSObject { NSString* manufacturer; NSString* model; }

  • (NSString *)getManufacturer;
  • (NSString *)getModel;
  • (void)setManufacturer:(NSString *)input;
  • (void)setModel:(NSString *)input;

@end {{< / highlight >}}

Car.m {{< highlight objc >}}

import “Car.h”

@implementation Car

  • (NSString *)getManufacturer { return manufacturer; }
  • (NSString *)getModel { return model; }
  • (void)setManufacturer:(NSString *)input { if(input != manufacturer) { [manufacturer release]; manufacturer = [input retain]; } }
  • (void)setModel:(NSString *)input { if(input != model) { [model release]; model = [input retain]; } }
  • (void)dealloc { [model release]; [manufacturer release]; }

@end

{{< / highlight >}}

Explicando o código, os dois primeiros métodos são apenas getters, sem nada de especial. Já nos setters, eu faço primeiro a verificação se os objetos tem a mesma referência. Isso deve-se ao fato de que, se eu fizer release no objeto antigo (manufacturer), eu também estou fazendo release no objeto novo (input).

Quando eles forem diferentes, faço o release do objeto antigo e faço o retain no novo, simples. Seguindo essa ideia, podemos ver que gerência de memória nos vem de graça quando usamos getters e setters, cabe ao programador gerenciar apenas o que está em seu escopo, como vemos no exemplo:

{{< highlight objc >}}

Car *car = [[Car alloc] init];

// Exemplo apenas ilustrativo
NSString* model = [[NSString alloc] initWithString:@"Carrera"];
[car setModel:model];
[model release];


[car release];

{{< / highlight >}}

Finalmente, properties

Properties são um jeito em Objective-C de se criar getters e setters, e de graça ganhar um “syntax sugar”. Vejamos o exemplo Car, reescrito:

Car.h {{< highlight objc >}}

import <Cocoa/Cocoa.h>

@interface Car : NSObject { NSString* manufacturer; NSString* model; }

@property (retain) manufacturer; @property (retain) model;

@end {{< / highlight >}}

Car.m {{< highlight objc >}}

import “Car.h”

@implementation Car

@synthesize manufacturer, model;

  • (void)dealloc { [self setModel:nil]; [self setManufacturer:nil]; [super dealloc]; }

@end

{{< / highlight >}}

O @property serve para declarar a interface e também comportamentos dessa propriedade. Nesse caso, estamos declarando o atributo retain que define que os métodos setters usem retain. Existem vários atributos possíveis:

  • retain: Utiliza retain para associar, bem similar com o que fizemos anteriormente;
  • assign: Faz simplesmente o assign simples, serve para tipos simples (NSInteger/int, por exemplo) ou quando tiver GC ligado;
  • nonatomic: Bastante utilizado no iOS, não utiliza locks para para fazer associações (multi-thread);
  • copy: Chama o método copy para criar um objeto clone do parâmetro.

Existem alguns outros, vale a pena ler a documentação oficial para saber mais: Declared properties attributes.

Na implementação, usamos o @synthesize para informar ao compilador que ele deve sintetizar os métodos, caso você não o faça.

Importante: O @synthesize não implementa nada para o dealloc, por isso você precisa fazer o release manualmente, como feito no exemplo acima.

Syntax Sugar

A property ainda introduz um sintax sugar mais familiar para os Rubistas. Ao invés de ter que fazer:

Antigo: {{< highlight objc >}} [car setModel:model]; {{< / highlight >}}

Novo: {{< highlight objc >}} car.model = model; {{< / highlight >}}

Para obter o valor é a mesma coisa. Isso facilita para pessoas que vem de outra linguagem e diminui o “barulho” da sintaxe com colchetes, que algumas pessoas não gostam. Compare:

Colchetes: {{< highlight objc >}} homeButton = … [[self getNavigationItem] setLeftBarButtonItem:homeButton]; {{< / highlight >}}

“Dot-syntax”: {{< highlight objc >}} homeButton = … self.navigationItem.leftBarButtonItem = homeButton; {{< / highlight >}}

Isso encerra o conteúdo sobre properties em Objective-C. Esse post foi basicamente uma compilação das seguintes referências:

Apêndice: Autorelease Pools

Uma coisa que eu não mencionei no artigo é a possibilidade de usar autorelease, conforme demonstrado abaixo:

{{< highlight objc >}} NSString* anotherCoolString = [[[NSString alloc] initWithString:@“Cool string!”] autorelease]; {{< / highlight >}}

O autorelease coloca o objeto em um “autorelease pool”, no qual vai ser liberado automaticamente pelo sistema poucos momentos depois (mais detalhes virão). Mas há de se pensar, qual a diferença de um garbage collector?

A diferença é que o autorelease pool não verifica se há referências ao objeto. Uma vez que o autorelease pool decida que vai liberar a referência do objeto, outras referências automaticamente se tornarão inválidas. Em contrapartida, o Garbage collector só vai liberar se não houver referências.

Os “autorelease pools” são criados no início de um event-loop e esvaziados no final quando está sendo usado algum Kit, como iOS ou Mac. Quando se faz programas de linha de comando, é necessário cria-las e esvazia-las manualmente.

Mas então quer dizer que eu posso simplesmente sair chamando :autorelease nos objetos? Não. O problema de se usar o autorelease pool é o alto consumo de memória se não for controlado e o fato do autorelease pool ser constantemente esvaziado pode causar inconsistências na sua app — objetos serem liberados da memória antes do previsto.

Mais detalhes na documentação oficial.


Vinicius Baggio Fuentes

Written by Vinicius Baggio Fuentes, who works in tech in NY but would rather be in the kitchen all the time. Twitter Instagram