2013-09-07 16 views
17

C'è una classe che assomiglia a questo (sto omettendo le importazioni per brevità):Override proprietà di sola lettura nella sottoclasse

Base.h:

@interface Base : NSObject 
@property (strong, readonly) NSString *something; 
- (id)initWithSomething:(NSString *)something; 

@end

Base. m:

@implementation Base 
- (id)initWithSomething:(NSString *)something { 
    self = [super init]; 
    if (self) _something = something; 
    return self; 
} 
@end 

Come si vede, la proprietà 'qualcosa' è di sola lettura. Ora voglio creare una sottoclasse che sostituisce quella proprietà per essere scrivibile così:

Sub.h:

@interface Sub : Base 
@property (strong) NSString *something; 
@end 

Sub.m:

@implementation Sub 
@end 

E il codice:

main.c:

int main(int argc, const char * argv[]) { 
    @autoreleasepool { 
     Sub *o = [Sub new]; 
     o.something = @"foo"; 
     NSLog(@"%@", o.something); 
    } 
    return 0; 
} 

Questo codice si traduce in:

2013-09-07 13:58:36.970 ClilTest[3094:303] *** Terminating app due to uncaught 
    exception 'NSInvalidArgumentException', reason: '-[Sub setSomething:]: unrecognized 
    selector sent to instance 0x100109ff0' 

Perché? Perché non trova setSelector?

Quando faccio questo nella sottoclasse invece:

Sub.m:

@implementation Sub 
@synthesize something = _something; 
@end 

funziona il tutto. Significa che la proprietà della sottoclasse non è sintetizzata per impostazione predefinita anche se è definita come @property nello @interface? La compilazione in qualche modo 'vede' il getter generato automaticamente da Base e non genera il setter? E perché, penso che il setter dovrebbe essere generato perché non esiste ancora. Sto usando Xcode 4.6.2 e il progetto è uno strumento Cli (tipo Foundation), ma lo stesso accade nel mio progetto attuale che è un'app per iPhone.

Sfondo: Ho un oggetto pesante (istanza di Base) che richiede una connessione Bluetooth ad alcune apparecchiature e dovrei creare un controller di visualizzazione per alcune funzionalità. Per test facili non voglio essere connesso a BT (in realtà, avrei bisogno di un dispositivo fisico e testare il codice su di esso), mi piacerebbe poterlo testare nel simulatore. Quello che mi è venuto in mente è che semplicemente creo una sottoclasse (Sub) che stuba alcuni metodi/proprietà e la uso invece, e quando il codice è pronto rimuovo solo il codice per la sottoclasse, sostituisco l'istanza con quella corretta , prova con un dispositivo, commetti e spinge. Funziona davvero bene, tranne che per la cosa strana con @property sopra.

Qualcuno potrebbe dirmi che cosa sta succedendo con la proprietà prioritaria?

+0

Oltre alla soluzione di lavoro, volevo aggiungere che "aumentare" il livello di privacy di un attributo va contro i principi OOP. Di solito (ma non sempre), se devi rendere una proprietà più visibile (privata -> protetta, o, protetta -> pubblica) in una sottoclasse, allora qualcosa non va nel modello. L'opposto accade con i metodi. Se la classe genitore ha il metodo A, non devi rendere A più privato (pubblico -> protetto, o, protetto -> privato) in una sottoclasse. –

risposta

28

Per una proprietà readonly, viene sintetizzato solo un metodo getter, ma nessun metodo setter.

E durante la compilazione della sottoclasse, il compilatore non sa come la proprietà è realizzata nella classe base (che potrebbe essere una consuetudine getter al posto di una variabile di istanza supporto). Quindi non può semplicemente creare un metodo setter nella sottoclasse.


Se si vuole avere accesso in scrittura alla variabile stessa istanza dalla sottoclasse, si deve dichiarare come @protected nella classe base (in modo che sia accessibile nella sottoclasse), re dichiarare queste proprietà come lettura-scrittura nella sottoclasse, e fornire un metodo setter:

Base.h:

@interface Base : NSObject { 
@protected 
    NSString *_something; 
} 
@property (strong, readonly) NSString *something; 
- (id)initWithSomething:(NSString *)something; 
@end 

Sub.h:

@interface Sub : Base 
@property (strong, readwrite) NSString *something; 
@end 

Sub.m:

@implementation Sub 
-(void)setSomething:(NSString *)something 
{ 
    _something = something; 
} 
@end 

La soluzione

@synthesize something = _something; 

genera getter e setter metodo nella sottoclasse, utilizzando una variabile di istanza separata_something nella sottoclasse (che è di fferent da _something nella classe base).

Anche questo funziona, è necessario tenere presente che self.something fa riferimento a variabili di istanza diverse nella classe base e nella sottoclasse. Per fare che più evidente, si potrebbe utilizzare una variabile di istanza diverso nella sottoclasse:

@synthesize something = _somethingElse; 
+0

Perché il compilatore non sa come è stato creato il getter? Sta compilando lo stesso progetto, quindi potrebbe potenzialmente ottenere quell'informazione. La prima opzione che rende il campo \ @protected in realtà non aiuta, il setter non è ancora generato. Strano, posso compilarlo senza problemi, e sto usando le impostazioni predefinite per tutto. Penserei che dal momento che l'iVar generato è \ @private, una sottoclasse può generare il proprio @private _qualcosa in quanto queste sono in realtà variabili differenti. Questo è almeno il modo in cui funzionano le cose, per esempio, Java. Sei sicuro di aver provato a compilare il mio codice esattamente? – wujek

+0

@wujek: Sì, ne sono abbastanza sicuro. Ho anche testato il mio codice prima di pubblicarlo. Avete qualche istruzione '@ synthesize' per la proprietà in Base.m? –

+0

Questo è molto strano, perché è esattamente ciò che funziona per me al momento. Posso condividere il mio progetto se vuoi dare un'occhiata. – wujek

-1

Immagino che le variabili di accompagnamento siano le stesse quando la proprietà non è sintetizzata nella sottoclasse. Quindi in fase di esecuzione il programma tenta di chiamare il setSomething nella superclasse. Ma dal momento che non esiste, viene lanciata un'eccezione.

2

Il dato risposta funziona perfettamente bene. Questa è una risposta alternativa, apparentemente Apple likes a bit more.

È possibile definire una private extension della classe, un file Base+Protected.h, che deve essere incluso nel Base.m e Sub.m.

Quindi, in questo nuovo file, si ridefinisce la proprietà come readwrite.

@interface Base() 
@property (strong, readwrite) NSString *something; 
@end 

Questa alternativa consente di utilizzare la funzione di accesso rathern self.something rispetto alla ivar _something.

Nota: è ancora necessario mantenere la definizione di something nel proprio Base.h così com'è.

Problemi correlati