Propriedades somente leitura em Objective-C?

93

Eu declarei uma propriedade somente leitura em minha interface como tal:

 @property (readonly, nonatomic, copy) NSString* eventDomain;

Talvez eu esteja entendendo mal as propriedades, mas pensei que, quando você declara como readonly, pode usar o setter gerado dentro do .marquivo de implementação ( ), mas as entidades externas não podem alterar o valor. Esta pergunta do SO diz que é isso que deve acontecer. Esse é o comportamento que procuro. No entanto, ao tentar usar o setter padrão ou sintaxe de ponto para definir eventDomaindentro do meu método init, recebo um unrecognized selector sent to instance.erro. Claro que estou @synthesizeroubando a propriedade. Tentando usá-lo assim:

 // inside one of my init methods
 [self setEventDomain:@"someString"]; // unrecognized selector sent to instance error

Então, estou interpretando mal a readonlydeclaração de uma propriedade? Ou algo mais está acontecendo?

Alex
fonte

Respostas:

116

Você precisa dizer ao compilador que também deseja um configurador. Uma maneira comum é colocá-lo em uma extensão de classe no arquivo .m:

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end
Eiko
fonte
22
Não é uma categoria. É uma extensão da classe (como disse Eiko). Eles são claramente diferentes, embora sejam usados ​​para fins semelhantes. Você não poderia, por exemplo, fazer o acima em uma categoria verdadeira.
bbum
2
Percebi que uma classe que herda de uma classe que possui propriedades de extensão de classe não as verá. ou seja, herança ver apenas a interface externa. confirme?
Yogev Shelly
5
Isso não é muito justo. Pode ser diferente, mas é conhecido como a "categoria anônima"
MaxGabriel
3
Isso é um acréscimo à declaração inicial do op na interface pública? Ou seja, essa declaração de extensão de classe substitui a declaração inicial no .h? Caso contrário, não vejo como isso exporia um setter público. Obrigado
Madbreaks
4
@Madbreaks É adicional, mas está no arquivo .m. Portanto, o compilador sabe como fazê-lo ler e escrever para aquela classe, mas o uso externo ainda é limitado a somente leitura conforme o esperado.
Eiko
38

Eiko e outros deram respostas corretas.

Esta é uma maneira mais simples: Acesse diretamente a variável de membro privado.

Exemplo

No arquivo .h do cabeçalho:

@property (strong, nonatomic, readonly) NSString* foo;

No arquivo .m de implementação:

// inside one of my init methods
self->_foo = @"someString"; // Notice the underscore prefix of var name.

É isso, é tudo de que você precisa. Sem confusão, sem confusão.

Detalhes

A partir do Xcode 4.4 e do LLVM Compiler 4.0 ( novos recursos no Xcode 4.4 ), você não precisa mexer nas tarefas discutidas nas outras respostas:

  • A synthesizepalavra-chave
  • Declarando uma variável
  • Declarando novamente a propriedade no arquivo .m de implementação.

Depois de declarar uma propriedade foo, você pode assumir Xcode acrescentou uma variável de membro particular chamado com um prefixo de sublinhado: _foo.

Se a propriedade foi declarada readwrite, o Xcode gera um método getter chamado fooe um setter chamado setFoo. Esses métodos são chamados implicitamente quando você usa a notação de ponto (meu Object.myMethod). Se a propriedade foi declarada readonly, nenhum setter é gerado. Isso significa que a variável de apoio, nomeada com o sublinhado, não é somente leitura. Os readonlymeios simplesmente que nenhum método de ajuste foi sintetizada, e, portanto, usando a notação de pontos para definir um valor de falha com um erro do compilador. A notação de ponto falha porque o compilador o impede de chamar um método (o setter) que não existe.

A maneira mais simples de contornar isso é acessar diretamente a variável de membro, nomeada com o sublinhado. Você pode fazer isso mesmo sem declarar essa variável com nome de sublinhado! O Xcode está inserindo essa declaração como parte do processo de construção / compilação, então seu código compilado realmente terá a declaração da variável. Mas você nunca vê essa declaração em seu arquivo de código-fonte original. Não mágico, apenas açúcar sintático .

Usar self->é uma forma de acessar uma variável de membro do objeto / instância. Você pode omitir isso e apenas usar o nome var. Mas eu prefiro usar a seta self + porque ela torna meu código autodocumentado. Quando você vê o, self->_foovocê sabe sem ambigüidade que _fooé uma variável de membro nesta instância.


By the way, discussão de prós e contras de assessores da propriedade versus acesso Ivar direta é exatamente o tipo de tratamento pensativo você vai ler na Dr. Matt Neuberg 's Programming iOS livro. Achei muito útil ler e reler.

Basil Bourque
fonte
1
Talvez você deva adicionar que nunca se deve acessar uma variável de membro diretamente (sem sua classe)! Fazer isso é uma violação de OOP (ocultação de informações).
TEM
2
@HAS correto, o cenário aqui é definir em particular a variável de membro não vista de fora desta classe. Esse é todo o ponto da pergunta do autor da postagem original: declarar uma propriedade como readonlytal que nenhuma outra classe pode defini-la.
Basil Bourque de
Eu estava olhando esta postagem para saber como criar uma propriedade somente leitura. Isso não funciona para mim. Isso me permite atribuir o valor no init, mas também posso alterar a _variable posteriormente com uma atribuição. ele não gera um erro ou aviso do compilador.
netskink
@BasilBourque Muito obrigado pela edição útil nessa questão de "persistência"!
GhostCat
36

Outra maneira que descobri de trabalhar com propriedades somente leitura é usar @synthesize para especificar o armazenamento de apoio. Por exemplo

@interface MyClass

@property (readonly) int whatever;

@end

Então, na implementação

@implementation MyClass

@synthesize whatever = m_whatever;

@end

Seus métodos podem então definir m_whatever, uma vez que é uma variável de membro.


Outra coisa interessante que percebi nos últimos dias é que você pode criar propriedades somente leitura que podem ser gravadas por subclasses como:

(no arquivo de cabeçalho)

@interface MyClass
{
    @protected
    int m_propertyBackingStore;
}

@property (readonly) int myProperty;

@end

Então, na implementação

@synthesize myProperty = m_propertyBackingStore;

Ele usará a declaração no arquivo de cabeçalho, de forma que as subclasses possam atualizar o valor da propriedade, enquanto retém sua leitura somente.

No entanto, um pouco lamentavelmente em termos de ocultação e encapsulamento de dados.

você não
fonte
1
A primeira maneira que você descreveu é mais fácil de lidar do que a resposta aceita. Apenas um "@synthesize foo1, foo2, foo3;" no .m, e está tudo bem.
sudo de
20

Consulte Personalização de classes existentes nos documentos do iOS.

readonly Indica que a propriedade é somente leitura. Se você especificar somente leitura, apenas um método getter será necessário na @implementation. Se você usar @synthesize no bloco de implementação, apenas o método getter é sintetizado. Além disso, se você tentar atribuir um valor usando a sintaxe de ponto, obterá um erro do compilador.

As propriedades somente leitura têm apenas um método getter. Você ainda pode definir o ivar de apoio diretamente na classe da propriedade ou usando a codificação de valor-chave.

Jonah
fonte
9

Você está entendendo mal a outra questão. Nessa questão há uma extensão de classe, declarada assim:

@interface MYShapeEditorDocument ()
@property (readwrite, copy) NSArray *shapesInOrderBackToFront;
@end

Isso é o que gera o setter visível apenas na implementação da classe. Portanto, como diz Eiko, você precisa declarar uma extensão de classe e substituir a declaração de propriedade para dizer ao compilador para gerar um setter apenas dentro da classe.

BoltClock
fonte
5

A solução mais curta é:

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end
user2159978
fonte
2

Se uma propriedade for definida como somente leitura, isso significa que efetivamente não haverá um configurador que possa ser usado internamente para a classe ou externamente para outras classes. (ou seja: você só terá um "getter" se isso fizer sentido.)

Pelo que parece, você deseja uma propriedade normal de leitura / gravação marcada como privada, que pode ser obtida definindo a variável de classe como privada em seu arquivo de interface da seguinte forma:

@private
    NSString* eventDomain;
}
John Parker
fonte