Onde colocar o iVars no “moderno” Objective-C?

O livro “iOS6 by Tutorials”, de Ray Wenderlich, tem um belo capítulo sobre como escrever código “moderno” de Objective-C. Em uma seção, os livros descrevem como mover o iVars do header da class para o arquivo de implementação. Como todos os iVars devem ser privados, isso parece ser a coisa certa a fazer.

Mas até agora eu encontrei 3 maneiras de fazer isso. Todo mundo está fazendo isso de forma diferente.

1.) Coloque iVars em @implementantion dentro de um bloco de chaves (é assim que é feito no livro).

2.) Coloque iVars sob @implementantion sem bloco de chaves

3.) Coloque o iVars dentro da interface privada acima do @implementantion (uma extensão de class)

Todas essas soluções parecem funcionar bem e até agora não notei nenhuma diferença no comportamento do meu aplicativo. Eu acho que não há maneira “certa” de fazer isso, mas eu preciso escrever alguns tutoriais e eu quero escolher apenas uma maneira para o meu código.

Qual caminho devo seguir?

Edit: Eu estou falando apenas sobre o iVars aqui. Não propriedades. Apenas variables ​​adicionais que o object precisa apenas para si e que não devem ser expostas ao exterior.

Amostras de código

1)

#import "Person.h" @implementation Person { int age; NSString *name; } - (id)init { self = [super init]; if (self) { age = 40; name = @"Holli"; } return self; } @end 

2)

 #import "Person.h" @implementation Person int age; NSString *name; - (id)init { self = [super init]; if (self) { age = 40; name = @"Holli"; } return self; } @end 

3)

 #import "Person.h" @interface Person() { int age; NSString *name; } @end @implementation Person - (id)init { self = [super init]; if (self) { age = 40; name = @"Holli"; } return self; } @end 

A capacidade de colocar variables ​​de instância no bloco @implementation , ou em uma extensão de class, é um recurso do “moderno tempo de execução Objective-C”, que é usado por todas as versões do iOS e por programas Mac OS X de 64 bits.

Se você quiser gravar aplicativos Mac OS X de 32 bits, deverá colocar suas variables ​​de instância na declaração @interface . No entanto, é provável que você não precise suportar uma versão de 32 bits do seu aplicativo. O OS X suporta aplicativos de 64 bits desde a versão 10.5 (Leopard), lançada há cinco anos.

Então, vamos supor que você esteja apenas escrevendo aplicativos que usarão o tempo de execução moderno. Onde você deve colocar seus ivars?

Opção 0: Na @interface (não faça isso)

Primeiro, vamos ver por que não queremos colocar variables ​​de instância em uma declaração @interface .

  1. Colocar variables ​​de instância em um @interface expõe detalhes da implementação para os usuários da class. Isso pode levar esses usuários (até mesmo a você mesmo quando usam suas próprias classs!) A confiar em detalhes de implementação que eles não deveriam. (Isso é independente de declararmos os ivars @private .)

  2. Colocar variables ​​de instância em uma @interface torna a compilation mais @interface , porque sempre que adicionamos, @interface ou removemos uma declaração ivar, temos que recompilar todos os arquivos .m que importam a interface.

Portanto, não queremos colocar variables ​​de instância na @interface . Onde devemos colocá-los?

Opção 2: no @implementation sem chaves (Don’t Do It)

Em seguida, vamos discutir sua opção 2, “Colocar o iVars em @implementantion sem um bloco de chaves”. Isso não declara variables ​​de instância! Você está falando sobre isso:

 @implementation Person int age; NSString *name; ... 

Esse código define duas variables ​​globais. Ele não declara nenhuma variável de instância.

É bom definir variables ​​globais em seu arquivo .m , mesmo em sua @implementation , se você precisar de variables ​​globais – por exemplo, porque você quer que todas as suas instâncias compartilhem algum estado, como um cache. Mas você não pode usar essa opção para declarar ivars, porque ela não declara ivars. (Além disso, as variables ​​globais particulares à sua implementação geralmente devem ser declaradas como static para evitar poluir o espaço de nomes global e arriscar erros de tempo de link.)

Isso deixa suas opções 1 e 3.

Opção 1: no @implementation com chaves (Do It)

Normalmente, queremos usar a opção 1: colocá-los em seu bloco @implementation principal, entre chaves, assim:

 @implementation Person { int age; NSString *name; } 

Nós as colocamos aqui porque mantêm sua existência privada, evitando os problemas que descrevi anteriormente, e porque geralmente não há razão para colocá-los em uma extensão de class.

Então, quando queremos usar sua opção 3, colocando-os em uma extensão de class?

Opção 3: em uma extensão de class (faça somente quando necessário)

Quase nunca há uma razão para colocá-los em uma extensão de turma no mesmo arquivo que a @implementation da @implementation . Poderíamos também colocá-los na @implementation nesse caso.

Mas, ocasionalmente, podemos escrever uma class grande o suficiente para dividir o código-fonte em vários arquivos. Podemos fazer isso usando categorias. Por exemplo, se estivéssemos implementando o UICollectionView (uma class bastante grande), poderíamos decidir que queremos colocar o código que gerencia as filas de visualizações reutilizáveis ​​(células e visualizações suplementares) em um arquivo de origem separado. Poderíamos fazer isso separando essas mensagens em uma categoria:

 // UICollectionView.h @interface UICollectionView : UIScrollView - (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout; @property (nonatomic, retain) UICollectionView *collectionViewLayout; // etc. @end @interface UICollectionView (ReusableViews) - (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier; - (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier; - (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier; - (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier; - (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath; - (id)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath; @end 

OK, agora podemos implementar os principais methods UICollectionView no UICollectionView.m e podemos implementar os methods que gerenciam visualizações reutilizáveis ​​no UICollectionView+ReusableViews.m , o que torna nosso código-fonte um pouco mais gerenciável.

Mas nosso código de gerenciamento de visualização reutilizável precisa de algumas variables ​​de instância. Essas variables ​​precisam ser expostas à class principal @implementation no UICollectionView.m , portanto, o compilador as emitirá no arquivo .o . E também precisamos expor essas variables ​​de instância ao código em UICollectionView+ReusableViews.m , para que esses methods possam usar os ivars.

É aqui que precisamos de uma extensão de class. Podemos colocar os ivars reusable-view-management em uma extensão de class em um arquivo de header privado:

 // UICollectionView_ReusableViewsSupport.h @interface UICollectionView () { NSMutableDictionary *registeredCellSources; NSMutableDictionary *spareCellsByIdentifier; NSMutableDictionary *registeredSupplementaryViewSources; NSMutableDictionary *spareSupplementaryViewsByIdentifier; } - (void)initReusableViewSupport; @end 

Não enviaremos este arquivo de header para usuários de nossa biblioteca. Vamos apenas importá-lo no UICollectionView.m e no UICollectionView+ReusableViews.m , para que tudo que precise ver esses ivars possa vê-los. Também lançamos um método que queremos que o método init principal chame para inicializar o código de gerenciamento de exibição reutilizável. Vamos chamar esse método de -[UICollectionView initWithFrame:collectionViewLayout:] em UICollectionView.m , e vamos implementá-lo em UICollectionView+ReusableViews.m .

A opção 2 está errada. Essas são variables ​​globais, não variables ​​de instância.

As opções 1 e 3 são essencialmente idênticas. Não faz absolutamente nenhuma diferença.

A escolha é se colocar variables ​​de instância no arquivo de header ou no arquivo de implementação. A vantagem de usar o arquivo de header é que você tem um atalho de teclado rápido e fácil (Command + Control + Up no Xcode) para visualizar e editar suas variables ​​de instância e declaração de interface.

A desvantagem é que você expõe os detalhes privados de sua turma em um header público. Isso não é desejável em alguns casos, especialmente se você estiver escrevendo código para outros usarem. Outro problema em potencial é que, se você estiver usando o Objective-C ++, é bom evitar colocar qualquer tipo de dados C ++ no seu arquivo de header.

Variáveis ​​de instância de implementação são ótimas opções para determinadas situações, mas para a maioria do meu código eu ainda coloco as variables ​​de instância no header simplesmente porque é mais conveniente para mim como um codificador trabalhando no Xcode. Meu conselho é fazer o que você achar mais conveniente para você.

Em grande parte, tem a ver com a visibilidade do ivar para subclasss. As subclasss não poderão acessar variables ​​de instância definidas no bloco @implementation .

Para código reutilizável que eu planejo distribuir (por exemplo, código de biblioteca ou framework) onde prefiro não expor variables ​​de instância para inspeção pública, então estou inclinado a colocar os ivars no bloco de implementação (sua opção 1).

Você deve colocar variables ​​de instância em uma interface privada acima da implementação. Opção 3.

A documentação para ler sobre isso é o guia de programação em Objective-C .

Da documentação:

Você pode definir variables ​​de instância sem propriedades

É uma boa prática usar uma propriedade em um object sempre que precisar rastrear um valor ou outro object.

Se você precisar definir suas próprias variables ​​de instância sem declarar uma propriedade, poderá incluí-las entre chaves no topo da interface de class ou implementação, desta forma:

Os ivars públicos devem realmente ser declarados propriedades na @interface (provavelmente o que você está pensando em 1). Os ivars privados, se você estiver executando o Xcode mais recente e usar o tempo de execução moderno (OS X ou iOS de 64 bits), podem ser declarados no @implementation (2), em vez de em uma extensão de class, o que é provável que você re pensando em em 3.