Proprietà di sola lettura in Objective-C?


93

Ho dichiarato una proprietà di sola lettura nella mia interfaccia come tale:

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

Forse sto fraintendendo le proprietà, ma ho pensato che quando lo dichiari come readonly, puoi usare il setter generato all'interno del .mfile implementation ( ), ma le entità esterne non possono cambiare il valore. Questa domanda SO dice che è quello che dovrebbe accadere. Questo è il comportamento che cerco. Tuttavia, quando si tenta di utilizzare lo standard setter o la sintassi del punto per impostare eventDomainall'interno del mio metodo init, mi dà un unrecognized selector sent to instance.errore. Certo che sto @synthesizeing la proprietà. Cercando di usarlo in questo modo:

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

Quindi sto fraintendendo la readonlydichiarazione su una proprietà? O sta succedendo qualcos'altro?

Risposte:


116

Devi dire al compilatore che vuoi anche un setter. Un modo comune è metterlo in un'estensione di classe nel file .m:

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end

22
Non è una categoria. È un'estensione di classe (come diceva Eiko). Sono nettamente diversi, sebbene utilizzati per scopi simili. Ad esempio, non potresti fare quanto sopra in una vera categoria.
bbum

2
Ho notato che una classe che eredita da una classe che ha proprietà di estensione di classe, non le vedrà. cioè l'ereditarietà vede solo l'interfaccia esterna. Confermare?
Yogev Shelly

5
Non è abbastanza giusto bbum. Potrebbe essere diverso, ma è noto come la "categoria Anonimo"
MaxGabriel

3
È in aggiunta alla dichiarazione iniziale dell'operazione nell'interfaccia pubblica? Significa, questa dichiarazione di estensione di classe sovrascrive la dichiarazione iniziale in .h? Altrimenti non vedo come questo esporrebbe un setter pubblico. Grazie
Madbreaks

4
@ Madbreaks È aggiuntivo, ma è nel file .m. Quindi il compilatore sa come farlo in lettura per quella classe, ma l'utilizzo esterno è ancora limitato alla sola lettura come previsto.
Eiko

38

Eiko e altri hanno dato le risposte corrette.

Ecco un modo più semplice: accedi direttamente alla variabile del membro privato.

Esempio

Nel file .h dell'intestazione:

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

Nel file .m di implementazione:

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

È tutto, è tutto ciò di cui hai bisogno. Niente confusione, niente confusione.

Dettagli

A partire da Xcode 4.4 e LLVM Compiler 4.0 ( nuove funzionalità in Xcode 4.4 ), non è necessario pasticciare con le faccende discusse nelle altre risposte:

  • Il synthesize parola chiave
  • Dichiarare una variabile
  • Ri-dichiarazione della proprietà nel file .m di implementazione.

Dopo aver dichiarato una proprietà foo, puoi presumere che Xcode abbia aggiunto una variabile membro privata denominata con un prefisso di sottolineatura:_foo .

Se la proprietà è stata dichiarata readwrite, Xcode genera un metodo getter denominato fooe un setter denominato setFoo. Questi metodi vengono chiamati implicitamente quando si utilizza la notazione punto (my Object.myMethod). Se la proprietà è stata dichiarata readonly, non viene generato alcun setter. Ciò significa che la variabile di supporto, denominata con il carattere di sottolineatura, non è di per sé di sola lettura. Ilreadonly mezzi semplicemente che nessun metodo setter è stato sintetizzato, e quindi utilizzando la notazione punto per impostare un valore non riesce con un errore di compilazione. La notazione del punto fallisce perché il compilatore ti impedisce di chiamare un metodo (il setter) che non esiste.

Il modo più semplice per aggirare questo problema è accedere direttamente alla variabile membro, denominata con il carattere di sottolineatura. Puoi farlo anche senza dichiarare quella variabile con nome di sottolineatura! Xcode sta inserendo quella dichiarazione come parte del processo di compilazione / compilazione, quindi il codice compilato avrà effettivamente la dichiarazione della variabile. Ma non vedi mai quella dichiarazione nel file del codice sorgente originale. Non magia, solo zucchero sintattico .

L'utilizzo self->è un modo per accedere a una variabile membro dell'oggetto / istanza. Potresti essere in grado di ometterlo e usare semplicemente il nome var. Ma preferisco usare l'auto + freccia perché rende il mio codice auto-documentante. Quando vedi lo self->_foosai senza ambiguità che _fooè una variabile membro in questa istanza.


Tra l'altro, la discussione dei pro e contro di accesso alle proprietà contro l'accesso diretto Ivar è esattamente il tipo di trattamento premuroso potrai leggere in Dr. Matt Neuberg s' Programmazione iOS libro. Ho trovato molto utile leggere e rileggere.


1
Forse dovresti aggiungere che non si dovrebbe mai accedere direttamente a una variabile membro (senza la sua classe)! Farlo è una violazione dell'OOP (information hiding).
HA

2
@ HA Corretto, lo scenario qui sta impostando privatamente la variabile membro non vista dall'esterno di questa classe. Questo è l'intero punto di domanda del poster originale: Dichiarare una proprietà come readonlyin modo che nessun altro classe può impostarla.
Basil Bourque

Stavo guardando questo post per come creare una proprietà di sola lettura. Questo non funziona per me. Mi consente di assegnare il valore nell'init, ma posso anche modificare la _variable in seguito con un'assegnazione. non genera un errore o un avviso del compilatore.
netskink

@BasilBourque Grazie mille per l'utile modifica su quella domanda sulla "persistenza"!
GhostCat

36

Un altro modo in cui ho scoperto di lavorare con le proprietà di sola lettura è usare @synthesize per specificare l'archivio di backup. Per esempio

@interface MyClass

@property (readonly) int whatever;

@end

Poi nella realizzazione

@implementation MyClass

@synthesize whatever = m_whatever;

@end

I tuoi metodi possono quindi essere impostati m_whatever, poiché è una variabile membro.


Un'altra cosa interessante che ho realizzato negli ultimi giorni è che puoi creare proprietà di sola lettura scrivibili da sottoclassi come queste:

(nel file di intestazione)

@interface MyClass
{
    @protected
    int m_propertyBackingStore;
}

@property (readonly) int myProperty;

@end

Quindi, nell'implementazione

@synthesize myProperty = m_propertyBackingStore;

Utilizzerà la dichiarazione nel file di intestazione, quindi le sottoclassi possono aggiornare il valore della proprietà, pur conservandone la sola lettura.

Un po 'purtroppo in termini di occultamento e incapsulamento dei dati.


1
Il primo modo che hai descritto è più facile da affrontare rispetto alla risposta accettata. Solo un "@synthesize foo1, foo2, foo3;" in .m, e tutto va bene.
sudo

20

Vedi Personalizzazione delle classi esistenti nei documenti iOS.

readonly Indica che la proprietà è di sola lettura. Se specifichi readonly, è richiesto solo un metodo getter in @implementation. Se usi @synthesize nel blocco di implementazione, viene sintetizzato solo il metodo getter. Inoltre, se si tenta di assegnare un valore utilizzando la sintassi del punto, si ottiene un errore del compilatore.

Le proprietà di sola lettura hanno solo un metodo getter. È comunque possibile impostare l'ivar di supporto direttamente nella classe della proprietà o utilizzando la codifica del valore chiave.


9

Stai fraintendendo l'altra domanda. In quella domanda c'è un'estensione di classe, dichiarata così:

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

Questo è ciò che genera il setter visibile solo all'interno dell'implementazione della classe. Quindi, come dice Eiko, è necessario dichiarare un'estensione di classe e sovrascrivere la dichiarazione della proprietà per dire al compilatore di generare un setter solo all'interno della classe.


5

La soluzione più breve è:

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end

2

Se una proprietà è definita come di sola lettura, ciò significa che non ci sarà effettivamente un setter che può essere utilizzato internamente alla classe o esternamente da altre classi. (cioè: avrai solo un "getter" se ha senso.)

Dai suoni di esso, vuoi una normale proprietà di lettura / scrittura contrassegnata come privata, che puoi ottenere impostando la variabile di classe come privata nel tuo file di interfaccia come tale:

@private
    NSString* eventDomain;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.