Declaração / definição de localizações de variáveis ​​em ObjectiveC?


113

Desde que comecei a trabalhar em aplicativos iOS e objetivo C, fico muito intrigado com os diferentes locais onde se pode declarar e definir variáveis. Por um lado temos a abordagem C tradicional, por outro temos as novas diretivas ObjectiveC que adicionam OO em cima disso. Vocês poderiam me ajudar a entender as melhores práticas e situações em que eu gostaria de usar esses locais para minhas variáveis ​​e talvez corrigir meu entendimento atual?

Aqui está um exemplo de classe (.h e .m):

#import <Foundation/Foundation.h>

// 1) What do I declare here?

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

// 3) class-specific method / property declarations

@end

e

#import "SampleClass.h"

// 4) what goes here?

@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

@implementation SampleClass
{
    // 6) define ivars
}

// 7) define methods and synthesize properties from both public and private
//    interfaces

@end
  • Meu entendimento de 1 e 4 é que essas são declarações e definições baseadas em arquivo de estilo C que não têm nenhuma compreensão do conceito de classe e, portanto, devem ser usadas exatamente como seriam usadas em C. Eu as vi usado para implementar singletons baseados em variáveis ​​estáticas antes. Há outros usos convenientes que estou perdendo?
  • Minha opinião sobre o trabalho com o iOS é que ivars quase foram completamente eliminados fora da diretiva @synthesize e, portanto, podem ser quase totalmente ignorados. É esse o caso?
  • Com relação a 5: por que eu iria querer declarar métodos em interfaces privadas? Meus métodos de classe privada parecem compilar bem sem uma declaração na interface. É principalmente para facilitar a leitura?

Muito obrigado, pessoal!

Respostas:


154

Eu posso entender sua confusão. Especialmente porque as atualizações recentes do Xcode e do novo compilador LLVM mudaram a maneira como ivars e propriedades podem ser declaradas.

Antes do "moderno" Objective-C (no "velho" Obj-C 2.0) você não tinha muitas opções. Variáveis ​​de instância costumavam ser declaradas no cabeçalho entre as chaves { }:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@end

Você conseguiu acessar essas variáveis ​​apenas em sua implementação, mas não de outras classes. Para fazer isso, você tinha que declarar métodos de acesso, que se parecem com isto:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}

- (int)myVar;
- (void)setMyVar:(int)newVar;

@end


// MyClass.m
@implementation MyClass

- (int)myVar {
   return myVar;
}

- (void)setMyVar:(int)newVar {
   if (newVar != myVar) {
      myVar = newVar;
   }
}

@end

Desta forma, você foi capaz de obter e definir essa variável de instância de outras classes também, usando a sintaxe usual de colchetes para enviar mensagens (métodos de chamada):

// OtherClass.m
int v = [myClass myVar];  // assuming myClass is an object of type MyClass.
[myClass setMyVar:v+1];

Porque declarar e implementar manualmente cada método acessador era muito chato, @propertye @synthesizeforam introduzidos para gerar automaticamente os métodos acessadores:

// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@synthesize myVar;
@end

O resultado é um código muito mais claro e curto. Os métodos de acesso serão implementados para você e você ainda pode usar a sintaxe de colchetes como antes. Além disso, você também pode usar a sintaxe de ponto para acessar as propriedades:

// OtherClass.m
int v = myClass.myVar;   // assuming myClass is an object of type MyClass.
myClass.myVar = v+1;

Desde o Xcode 4.4, você não precisa mais declarar uma variável de instância sozinho e também pode pular @synthesize. Se você não declarar um ivar, o compilador o adicionará para você e também gerará os métodos de acesso sem que você precise usar @synthesize.

O nome padrão para o ivar gerado automaticamente é o nome ou sua propriedade que começa com um sublinhado. Você pode alterar o nome do ivar gerado usando@synthesize myVar = iVarName;

// MyClass.h
@interface MyClass : NSObject 
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@end

Isso funcionará exatamente como o código acima. Por motivos de compatibilidade, você ainda pode declarar ivars no cabeçalho. Mas como a única razão pela qual você deseja fazer isso (e não declarar uma propriedade) é criar uma variável privada, agora você também pode fazer isso no arquivo de implementação e esta é a forma preferida.

Um @interfacebloco no arquivo de implementação é na verdade uma extensão e pode ser usado para encaminhar métodos de declaração (não são mais necessários) e para (re) declarar propriedades. Você poderia, por exemplo, declarar uma readonlypropriedade em seu cabeçalho.

@property (nonatomic, readonly) myReadOnlyVar;

e declare-o novamente em seu arquivo de implementação readwritepara poder configurá-lo usando a sintaxe de propriedade e não apenas via acesso direto ao ivar.

Quanto a declarar variáveis ​​completamente fora de qualquer bloco @interfaceou @implementation, sim, essas são variáveis ​​C simples e funcionam exatamente da mesma.


2
Ótima resposta! Observe também: stackoverflow.com/questions/9859719/…
— nycynik

44

Primeiro, leia a resposta de @BackmerB. É uma boa visão geral dos porquês e do que você deve fazer em geral. Com isso em mente, para suas perguntas específicas:

#import <Foundation/Foundation.h>

// 1) What do I declare here?

Nenhuma definição de variável real vai aqui (é tecnicamente legal fazer isso se você souber exatamente o que está fazendo, mas nunca faça isso). Você pode definir vários outros tipos de coisas:

  • Typdefs
  • enums
  • externos

Externs parecem declarações de variáveis, mas são apenas uma promessa de realmente declarar em outro lugar. Em ObjC, eles devem ser usados ​​apenas para declarar constantes e, geralmente, apenas constantes de string. Por exemplo:

extern NSString * const MYSomethingHappenedNotification;

Em seguida, você .mdeclararia em seu arquivo a constante real:

NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";

@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

Conforme observado por DrummerB, isso é um legado. Não coloque nada aqui.


// 3) class-specific method / property declarations

@end

Sim.


#import "SampleClass.h"

// 4) what goes here?

Constantes externas, conforme descrito acima. Além disso, as variáveis ​​estáticas do arquivo podem ser acessadas aqui. São equivalentes às variáveis ​​de classe em outras linguagens.


@interface SampleClass()

// 5) private interface, can define private methods and properties here

@end

Sim


@implementation SampleClass
{
    // 6) define ivars
}

Mas muito raramente. Quase sempre você deve permitir que o clang (Xcode) crie as variáveis ​​para você. As exceções geralmente são em torno de ivars não ObjC (como objetos Core Foundation, e especialmente objetos C ++ se esta for uma classe ObjC ++), ou ivars que têm semânticas de armazenamento estranhas (como ivars que não combinam com uma propriedade por algum motivo).


// 7) define methods and synthesize properties from both public and private
//    interfaces

Geralmente você não deve mais @synthesize. Clang (Xcode) fará isso por você, e você deve permitir.

Nos últimos anos, as coisas ficaram dramaticamente mais simples. O efeito colateral é que agora existem três eras diferentes (Fragile ABI, Non-fragile ABI, Non-fragile ABI + auto-syntheisze). Portanto, quando você vê o código mais antigo, pode ser um pouco confuso. Assim, confusão decorrente da simplicidade: D


Apenas pensando, mas por que não deveríamos sintetizar explicitamente? Eu faço isso porque acho meu código mais fácil de entender, especialmente quando algumas propriedades têm acessores sintetizados e algumas têm implementações personalizadas, já que estou acostumado a sintetizar. Existem desvantagens na síntese explícita?
— Metabble

O problema de usá-lo como documentação é que ele realmente não documenta nada. Apesar de usar sintetizar, você pode ter sobrescrito um ou ambos os acessadores. Não há como dizer a partir da linha de sintetização algo realmente útil. A única coisa pior do que nenhuma documentação é a documentação enganosa. Deixe isso de lado.
— Rob Napier

3
Por que o nº 6 é raro? Não é a maneira mais fácil de obter uma variável privada?
— pfrank

A maneira mais fácil e melhor de obter uma propriedade privada é a # 5.
— Rob Napier

1
@RobNapier Ainda é necessário usar @ synthesize às vezes (por exemplo, se uma propriedade for somente leitura, seu acessador será substituído)
— Andy

6

Eu também sou muito novo, então espero não estragar nada.

1 e 4: Variáveis ​​globais de estilo C: elas têm amplo escopo de arquivo. A diferença entre os dois é que, por serem extensos ao arquivo, o primeiro estará disponível para qualquer pessoa que importe o cabeçalho, enquanto o segundo não.

2: variáveis ​​de instância. A maioria das variáveis ​​de instância é sintetizada e recuperada / configurada por meio de acessadores usando propriedades porque torna o gerenciamento de memória simples e agradável, além de fornecer notação de ponto fácil de entender.

6: Os ivars de implementação são um tanto novos. É um bom lugar para colocar ivars privados, já que você deseja expor apenas o que é necessário no cabeçalho público, mas as subclasses não os herdam AFAIK.

3 e 7: Método público e declarações de propriedade, depois implementações.

5: Interface privada. Sempre uso interfaces privadas sempre que posso para manter as coisas limpas e criar uma espécie de efeito de caixa preta. Se eles não precisam saber sobre isso, coloque-o lá. Eu também faço isso para facilitar a leitura, não sei se há outros motivos.


1
Não pense que você estragou nada :) Alguns comentários - # 1 e # 4 esp com # 4 frequentemente você vê variáveis ​​de armazenamento estático. # 1 frequentemente você verá o armazenamento externo especificado e, em seguida, o armazenamento real alocado no # 4. # 2) apenas geralmente se uma subclasse precisar por algum motivo. # 5 não é mais necessário encaminhar métodos privados de declaração.
— Carl Veazey

Sim, eu mesmo verifiquei a declaração. Ele costumava dar um aviso se um método privado chamasse outro que foi definido depois dele sem uma declaração de encaminhamento, certo? Fiquei meio surpreso quando não me avisou.
— Metabble

Sim, é uma nova parte do compilador. Eles realmente fizeram muitos avanços ultimamente.
— Carl Veazey

6

Este é um exemplo de todos os tipos de variáveis ​​declaradas em Objective-C. O nome da variável indica seu acesso.

Arquivo: Animal.h

@interface Animal : NSObject
{
    NSObject *iProtected;
@package
    NSObject *iPackage;
@private
    NSObject *iPrivate;
@protected
    NSObject *iProtected2; // default access. Only visible to subclasses.
@public
    NSObject *iPublic;
}

@property (nonatomic,strong) NSObject *iPublic2;

@end

Arquivo: Animal.m

#import "Animal.h"

// Same behaviour for categories (x) than for class extensions ().
@interface Animal(){
@public
    NSString *iNotVisible;
}
@property (nonatomic,strong) NSObject *iNotVisible2;
@end

@implementation Animal {
@public
    NSString *iNotVisible3;
}

-(id) init {
    self = [super init];
    if (self){
        iProtected  = @"iProtected";
        iPackage    = @"iPackage";
        iPrivate    = @"iPrivate";
        iProtected2 = @"iProtected2";
        iPublic     = @"iPublic";
        _iPublic2    = @"iPublic2";

        iNotVisible   = @"iNotVisible";
        _iNotVisible2 = @"iNotVisible2";
        iNotVisible3  = @"iNotVisible3";
    }
    return self;
}

@end

Observe que as variáveis ​​iNotVisible não são visíveis em nenhuma outra classe. Este é um problema de visibilidade, portanto, declará-los com @propertyou @publicnão altera isso.

Dentro de um construtor, é uma boa prática acessar variáveis ​​declaradas com o @propertyuso de sublinhado, selfpara evitar efeitos colaterais.

Vamos tentar acessar as variáveis.

Arquivo: Cow.h

#import "Animal.h"
@interface Cow : Animal
@end

Arquivo: Cow.m

#import "Cow.h"
#include <objc/runtime.h>

@implementation Cow

-(id)init {
    self=[super init];
    if (self){
        iProtected    = @"iProtected";
        iPackage      = @"iPackage";
        //iPrivate    = @"iPrivate"; // compiler error: variable is private
        iProtected2   = @"iProtected2";
        iPublic       = @"iPublic";
        self.iPublic2 = @"iPublic2"; // using self because the backing ivar is private

        //iNotVisible   = @"iNotVisible";  // compiler error: undeclared identifier
        //_iNotVisible2 = @"iNotVisible2"; // compiler error: undeclared identifier
        //iNotVisible3  = @"iNotVisible3"; // compiler error: undeclared identifier
    }
    return self;
}
@end

Ainda podemos acessar as variáveis ​​não visíveis usando o tempo de execução.

Arquivo: Cow.m (parte 2)

@implementation Cow(blindAcess)

- (void) setIvar:(NSString*)name value:(id)value {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    object_setIvar(self, ivar, value);
}

- (id) getIvar:(NSString*)name {
    Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
    id thing = object_getIvar(self, ivar);
    return thing;
}

-(void) blindAccess {
    [self setIvar:@"iNotVisible"  value:@"iMadeVisible"];
    [self setIvar:@"_iNotVisible2" value:@"iMadeVisible2"];
    [self setIvar:@"iNotVisible3" value:@"iMadeVisible3"];
    NSLog(@"\n%@ \n%@ \n%@",
          [self getIvar:@"iNotVisible"],
          [self getIvar:@"_iNotVisible2"],
          [self getIvar:@"iNotVisible3"]);
}

@end

Vamos tentar acessar as variáveis ​​não visíveis.

Arquivo: main.m

#import "Cow.h"
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
    @autoreleasepool {
        Cow *cow = [Cow new];
        [cow performSelector:@selector(blindAccess)];
    }
}

Isto imprime

iMadeVisible 
iMadeVisible2 
iMadeVisible3

Observe que consegui acessar o ivar de apoio, _iNotVisible2que é privado da subclasse. Em Objective-C todas as variáveis ​​podem ser lidas ou definidas, mesmo aquelas que estão marcadas @private, sem exceções.

Não incluí objetos associados ou variáveis ​​C porque são pássaros diferentes. Quanto às variáveis ​​C, qualquer variável definida fora de @interface X{}ou @implementation X{}é uma variável C com escopo de arquivo e armazenamento estático.

Eu não discuti os atributos de gerenciamento de memória ou atributos somente leitura / leitura / gravação, getter / setter.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.