Questo thread è piuttosto vecchio e la maggior parte di ciò che voglio condividere è già qui.
Tuttavia, il mio metodo preferito non è menzionato, e AFAIK non c'è supporto nativo nell'attuale Clang, quindi eccomi qui ...
In primo luogo, e soprattutto (come altri hanno già sottolineato) le classi astratte sono qualcosa di molto raro in Objective-C - invece usiamo la composizione (a volte attraverso la delega). Questo è probabilmente il motivo per cui una tale funzionalità non esiste già nel linguaggio / compilatore - a parte le @dynamic
proprietà, che IIRC sono state aggiunte in ObjC 2.0 che accompagna l'introduzione di CoreData.
Ma dato che (dopo un'attenta valutazione della tua situazione!) Sei giunto alla conclusione che la delegazione (o la composizione in generale) non è adatta a risolvere il tuo problema, ecco come lo faccio:
- Implementa ogni metodo astratto nella classe base.
- Fai quella implementazione
[self doesNotRecognizeSelector:_cmd];
...
- …seguito da
__builtin_unreachable();
per mettere a tacere l'avvertimento che riceverai per i metodi non nulli, che ti dice "il controllo ha raggiunto la fine della funzione non nulla senza un ritorno".
- Combina i passaggi 2. e 3. in una macro o annota
-[NSObject doesNotRecognizeSelector:]
usando __attribute__((__noreturn__))
in una categoria senza implementazione in modo da non sostituire l'implementazione originale di quel metodo e includi l'intestazione per quella categoria nel PCH del tuo progetto.
Personalmente preferisco la versione macro in quanto mi consente di ridurre il più possibile la piastra di cottura.
Ecco qui:
// Definition:
#define D12_ABSTRACT_METHOD {\
[self doesNotRecognizeSelector:_cmd]; \
__builtin_unreachable(); \
}
// Usage (assuming we were Apple, implementing the abstract base class NSString):
@implementation NSString
#pragma mark - Abstract Primitives
- (unichar)characterAtIndex:(NSUInteger)index D12_ABSTRACT_METHOD
- (NSUInteger)length D12_ABSTRACT_METHOD
- (void)getCharacters:(unichar *)buffer range:(NSRange)aRange D12_ABSTRACT_METHOD
#pragma mark - Concrete Methods
- (NSString *)substringWithRange:(NSRange)aRange
{
if (aRange.location + aRange.length >= [self length])
[NSException raise:NSInvalidArgumentException format:@"Range %@ exceeds the length of %@ (%lu)", NSStringFromRange(aRange), [super description], (unsigned long)[self length]];
unichar *buffer = (unichar *)malloc(aRange.length * sizeof(unichar));
[self getCharacters:buffer range:aRange];
return [[[NSString alloc] initWithCharactersNoCopy:buffer length:aRange.length freeWhenDone:YES] autorelease];
}
// and so forth…
@end
Come puoi vedere, la macro fornisce la piena implementazione dei metodi astratti, riducendo al minimo assoluto la quantità necessaria di boilerplate.
Un'opzione ancora migliore sarebbe quella di esercitare pressioni sulla squadra Clang a fornire un attributo compilatore per questo caso, tramite richieste di funzionalità. (Meglio, perché ciò consentirebbe anche la diagnostica in fase di compilazione per quegli scenari in cui si esegue la sottoclasse, ad esempio NSIncrementalStore.)
Perché scelgo questo metodo
- Il lavoro viene svolto in modo efficiente e in qualche modo conveniente.
- È abbastanza facile da capire. (Va bene, questo
__builtin_unreachable()
potrebbe sorprendere le persone, ma è anche abbastanza facile da capire.)
- Non può essere rimosso nelle build di rilascio senza generare altri avvisi del compilatore o errori, a differenza di un approccio basato su una delle macro di asserzioni.
Quest'ultimo punto necessita di qualche spiegazione, immagino:
Alcune (la maggior parte?) Persone spogliano le affermazioni nelle build di rilascio. (Non sono d'accordo con quell'abitudine, ma questa è un'altra storia ...) Non riuscire a implementare un metodo richiesto - tuttavia - è cattivo , terribile , sbagliato e fondamentalmente la fine dell'universo per il tuo programma. Il tuo programma non può funzionare correttamente in questo senso perché è indefinito e il comportamento indefinito è la cosa peggiore di sempre. Pertanto, essere in grado di eliminare tali diagnosi senza generare nuova diagnostica sarebbe completamente inaccettabile.
È abbastanza grave che non è possibile ottenere un'adeguata diagnostica in fase di compilazione per tali errori del programmatore e ricorrere al rilevamento in fase di esecuzione per questi, ma se si può intonarci su di esso nelle build di rilascio, perché provare ad avere una classe astratta nel primo posto?