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!

Alexandr Kurilin
fonte

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.

Baterista b
fonte
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

Rob Napier
fonte
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.

Metabble
fonte
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.

Jano
fonte