Creazione di una classe astratta in Objective-C


507

Sono originariamente un programmatore Java che ora lavora con Objective-C. Vorrei creare una classe astratta, ma ciò non sembra possibile in Objective-C. È possibile?

In caso contrario, quanto vicino a una classe astratta posso ottenere in Objective-C?


18
Le risposte di seguito sono fantastiche. Trovo che il problema delle classi astratte sia tangenzialmente correlato ai metodi privati: entrambi sono metodi per limitare ciò che può fare il codice client, e nessuno dei due esiste in Objective-C. Penso che aiuta a capire che la mentalità del linguaggio stesso è fondamentalmente diversa da Java. Vedi la mia risposta a: stackoverflow.com/questions/1020070/#1020330
Quinn Taylor,

Grazie per le informazioni sulla mentalità della comunità Objective-C rispetto ad altre lingue. Questo risolve davvero una serie di domande correlate che ho avuto (come perché nessun meccanismo diretto per metodi privati, ecc.).
Jonathan Arbogast,

1
quindi dai un'occhiata al sito CocoaDev che fornisce un confronto java cocoadev.com/index.pl?AbstractSuperClass

2
Anche se Barry lo menziona come ripensamento (perdonami se lo sto leggendo male), penso che tu stia cercando un Protocollo nell'Obiettivo C. Vedi, ad esempio, Cos'è un protocollo? .
jww

Risposte:


633

In genere, la classe Objective-C è astratta solo per convenzione: se l'autore documenta una classe come astratta, semplicemente non la usa senza sottoclassarla. Tuttavia, non esiste un'applicazione per la compilazione che impedisce l'istanza di una classe astratta. In effetti, non c'è nulla che impedisca a un utente di fornire implementazioni di metodi astratti attraverso una categoria (cioè in fase di esecuzione). Puoi forzare un utente a sostituire almeno determinati metodi sollevando un'eccezione nell'implementazione di tali metodi nella tua classe astratta:

[NSException raise:NSInternalInconsistencyException 
            format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];

Se il tuo metodo restituisce un valore, è un po 'più facile da usare

@throw [NSException exceptionWithName:NSInternalInconsistencyException
                               reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
                             userInfo:nil];

come allora non è necessario aggiungere un'istruzione return dal metodo.

Se la classe astratta è davvero un'interfaccia (cioè non ha implementazioni di metodi concreti), l'uso di un protocollo Objective-C è l'opzione più appropriata.


18
Penso che la parte più appropriata della risposta sia stata menzionare che potresti usare @protocol invece di definire i metodi.
Chad Stewart,

13
Per chiarire: è possibile dichiarare i metodi in una @protocoldefinizione, ma non è possibile definire i metodi lì.
Richard,

Con un metodo di categoria su NSException + (instancetype)exceptionForCallingAbstractMethod:(SEL)selectorfunziona molto bene.
Patrick Pijnappel,

Questo ha funzionato per me dal momento che, IMHO, lanciare un'eccezione è più ovvio per gli altri sviluppatori che questo è un comportamento desiderato più che doesNotRecognizeSelector.
Chris,

Utilizzare i protocolli (per la classe completamente astratta) o il modello del modello di modello in cui la classe astratta ha una logica di implementazione / flusso parziale come indicato qui stackoverflow.com/questions/8146439/… . Vedi la mia risposta qui sotto.
Hadaytullah,

267

No, non è possibile creare una classe astratta in Objective-C.

Puoi deridere una classe astratta - facendo chiamare i metodi / selettori doesNotRecognizeSelector: e quindi sollevando un'eccezione che rende la classe inutilizzabile.

Per esempio:

- (id)someMethod:(SomeObject*)blah
{
     [self doesNotRecognizeSelector:_cmd];
     return nil;
}

Puoi anche farlo per init.


5
@Chuck, non ho effettuato il downgrade del voto, ma il NSObjectriferimento suggerisce di usarlo dove NON vuoi ereditare un metodo, non forzare l'override di un metodo. Anche se quelli possono essere la stessa cosa, forse :)
Dan Rosenstark,

Perché non dovresti usare solo un protocollo in questa istanza? Per me, è bello sapere solo per gli stub del metodo, ma non per un'intera classe astratta. Il caso d'uso per questo sembra limitato.
lewiguez,

Non l'ho sottovalutato, ma il suggerimento che dovresti causare un'eccezione nel metodo init è il motivo più probabile. Il formato più comune per una sottoclasse inizierà il proprio metodo init chiamando self = [super init] - che obbedientemente genererà un'eccezione. Questo formato funziona bene per la maggior parte dei metodi, ma non lo farei mai in qualsiasi cosa in cui la sottoclasse potrebbe chiamarsi super implementazione.
Xono,

5
Puoi assolutamente creare classi astratte in Objective-C, ed è abbastanza comune. Esistono diverse classi di framework Apple che fanno questo. Le classi astratte di Apple generano eccezioni specifiche (NSInvalidArgumentException), spesso chiamando NSInvalidAbstractInvocation (). Chiamare un metodo astratto è un errore di programmazione, motivo per cui genera un'eccezione. Le fabbriche astratte sono comunemente implementate come gruppi di classi.
Quellish

2
@quellish: Come hai detto: chiamare un metodo astratto è un errore di programmazione. Dovrebbe essere trattato come tale, invece di fare affidamento sulla segnalazione degli errori di runtime (NSException). Questo penso che sia il problema più grande per gli sviluppatori che provengono da altre lingue in cui astratto significa "non è possibile creare un'istanza di un oggetto di questo tipo" in Obj-C significa "le cose andranno male in fase di esecuzione quando si crea un'istanza di questa classe".
Cross_

60

Sto solo riflettendo sulla risposta di @Barry Wark sopra (e aggiornando per iOS 4.3) e lasciandolo per il mio riferimento:

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define methodNotImplemented() mustOverride()

quindi nei tuoi metodi puoi usarlo

- (void) someMethod {
     mustOverride(); // or methodNotImplemented(), same thing
}



Note: Non sono sicuro se far apparire una macro come una funzione C sia una buona idea o no, ma la terrò fino a quando non verrà educata al contrario. Penso che sia più corretto usare NSInvalidArgumentException(piuttosto che NSInternalInconsistencyException) dal momento che è quello che il sistema di runtime lancia in risposta alla doesNotRecognizeSelectorchiamata (vedi NSObjectdocumenti).


1
Certo, @TomA, spero che ti dia idee per altri codici che puoi macro. La mia macro più utilizzata è un semplice riferimento a un singleton: il codice dice universe.thingma si espande a [Universe universe].thing. Grande divertimento, risparmiando migliaia di lettere di codice ...
Dan Rosenstark,

Grande. Cambiato un po ', però: #define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil].
noamtm,

1
@Yar: Non la penso così. Usiamo __PRETTY_FUNCTION__ovunque, tramite una macro DLog (...), come suggerito qui: stackoverflow.com/a/969291/38557 .
noamtm,

1
per maggiori dettagli -#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
gbk

1
se aggiungi la classe base e poi erediti questa classe per 10 volte e hai dimenticato di implementarla in una delle classi otterrai un messaggio con il nome della classe Base non ereditato come quello Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'Nel caso in cui ti proponessi di ottenere anche HomeViewController - method not implementeddove HomeViewController ereditato da Base - questo fornirà ulteriori informazioni
gbk

42

La soluzione che mi è venuta in mente è:

  1. Crea un protocollo per tutto ciò che desideri nella tua classe "astratta"
  2. Crea una classe base (o forse chiamala astratta) che implementa il protocollo. Per tutti i metodi che desideri "astratti" implementali nel file .m, ma non nel file .h.
  3. Fai ereditare la tua classe figlio dalla classe base E implementa il protocollo.

In questo modo il compilatore ti darà un avviso per qualsiasi metodo nel protocollo che non è implementato dalla tua classe figlio.

Non è così succinto come in Java, ma ottieni l'avvertimento del compilatore desiderato.


2
+1 Questa è davvero la soluzione più vicina a una classe astratta in Java. Ho usato questo approccio da solo e funziona benissimo. È anche permesso nominare il protocollo come la classe base (proprio come ha fatto Apple NSObject). Se poi inserisci il protocollo e la dichiarazione della classe base nello stesso file di intestazione, è quasi indistinguibile da una classe astratta.
codingFriend1

Ah, ma la mia classe astratta implementa parte di un protocollo, il resto è implementato da sottoclassi.
Roger CS Wernersson,

1
potresti avere solo le classi figlio implementare il protocollo e lasciare i metodi della superclasse fuori tutti insieme invece che vuoti. quindi avere proprietà di tipo Superclass <MyProtocol>. inoltre, per una maggiore flessibilità, puoi aggiungere il prefisso ai tuoi metodi nel tuo protocollo con @optional.
dotToString

Questo non sembra funzionare in Xcode 5.0.2; genera solo un avviso per la classe "astratta". Non estendere la classe "astratta" genera il corretto avviso del compilatore, ma ovviamente non consente di ereditare i metodi.
Topher Fangio,

Preferisco questa soluzione ma davvero non mi piace, non è davvero una buona struttura di codice nel progetto. // dovrebbe usare questo come tipo di base per le sottoclassi. typedef BaseClass <BaseClassProtocol> BASECLASS; È solo una regola settimanale, non mi piace.
Itachi,

35

Dalla mailing list di Omni Group :

Attualmente Objective-C non ha il costrutto di compilatore astratto come Java.

Quindi, tutto ciò che devi fare è definire la classe astratta come qualsiasi altra classe normale e implementare gli stub dei metodi per i metodi astratti che sono vuoti o riportano il non supporto per il selettore. Per esempio...

- (id)someMethod:(SomeObject*)blah
{
     [self doesNotRecognizeSelector:_cmd];
     return nil;
}

Faccio anche quanto segue per impedire l'inizializzazione della classe astratta tramite l'inizializzatore predefinito.

- (id)init
{
     [self doesNotRecognizeSelector:_cmd];
     [self release];
     return nil;
}

1
Non avevo pensato di usare -doesNotRecognizeSelector: e in qualche modo mi piace questo approccio. Qualcuno sa come fare in modo che il compilatore emetta un avviso per un metodo "astratto" creato in questo modo o sollevando un'eccezione? Sarebbe fantastico ...
Quinn Taylor,

26
L'approccio doesNotRecognizeSelector impedisce il modello self = [super init] suggerito da Apple.
Dlinsin,

1
@david: sono abbastanza sicuro che il punto sia sollevare un'eccezione il prima possibile. Idealmente dovrebbe essere in fase di compilazione, ma poiché non c'è modo di farlo, si sono accontentati di un'eccezione di runtime. Questo è simile a un fallimento dell'asserzione, che non dovrebbe mai essere sollevato nel codice di produzione in primo luogo. In effetti, un'asserzione (falsa) potrebbe effettivamente essere migliore poiché è più chiaro che questo codice non dovrebbe mai essere eseguito. L'utente non può fare nulla per risolverlo, lo sviluppatore deve risolverlo. Pertanto sollevare un'eccezione o un'asserzione fallita qui sembra una buona idea.
Senso

2
Non penso che questa sia una soluzione praticabile in quanto le sottoclassi possono avere chiamate perfettamente legittime a [super init].
Raffi Khatchadourian,

@dlinsin: Questo è esattamente quello che vuoi quando la classe astratta non deve essere inizializzata tramite init. Le sue sottoclassi presumibilmente sanno quale metodo eccellente chiamare, ma questo interrompe l'invocazione negligente tramite "nuovo" o "alloc / init".
gnasher729,

21

Invece di provare a creare una classe base astratta, considera l'utilizzo di un protocollo (simile a un'interfaccia Java). Ciò consente di definire un insieme di metodi, quindi accettare tutti gli oggetti conformi al protocollo e implementare i metodi. Ad esempio, posso definire un protocollo operativo e quindi avere una funzione come questa:

- (void)performOperation:(id<Operation>)op
{
   // do something with operation
}

Dove op può essere qualsiasi oggetto che implementa il protocollo operativo.

Se hai bisogno della tua classe base astratta per fare qualcosa di più che semplicemente definire metodi, puoi creare una normale classe Objective-C e impedirne l'istanza. Basta sostituire la funzione - (id) init e farla restituire zero o asserire (false). Non è una soluzione molto pulita, ma poiché Objective-C è completamente dinamico, non esiste un equivalente diretto di una classe base astratta.


Per me, questo sembra essere il modo giusto di andare per i casi in cui useresti la classe astratta, almeno quando si intende veramente "interfaccia" (come in C ++). Ci sono aspetti negativi nascosti in questo approccio?
febbraio

1
@febeling, le classi astratte - almeno in Java - non sono solo interfacce. Definiscono anche alcuni (o quasi) comportamenti. Questo approccio potrebbe essere buono in alcuni casi, comunque.
Dan Rosenstark,

Ho bisogno di una classe di base per implementare alcune funzionalità che tutte le mie sottoclassi condividono (rimuovendo la duplicazione), ma ho anche bisogno di un protocollo per altri metodi che la base non dovrebbe gestire (la parte astratta). Quindi ho bisogno di usare entrambi, ed è qui che può essere difficile assicurarsi che le tue sottoclassi si stiano implementando correttamente.
LightningStryk

19

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 @dynamicproprietà, 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:

  1. Implementa ogni metodo astratto nella classe base.
  2. Fai quella implementazione [self doesNotRecognizeSelector:_cmd];...
  3. …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".
  4. 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

  1. Il lavoro viene svolto in modo efficiente e in qualche modo conveniente.
  2. È abbastanza facile da capire. (Va bene, questo __builtin_unreachable()potrebbe sorprendere le persone, ma è anche abbastanza facile da capire.)
  3. 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?


Questa è la mia nuova soluzione preferita - la __builtin_unreachable();gemma rende questo lavoro perfettamente. La macro lo rende auto-documentante e il comportamento corrisponde a ciò che accade se si chiama un metodo mancante su un oggetto.
Alex MDC,

12

Utilizzando @propertye @dynamicpotrebbe anche funzionare. Se dichiari una proprietà dinamica e non fornisci un'implementazione del metodo di corrispondenza, tutto verrà comunque compilato senza avvisi e otterrai un unrecognized selectorerrore in fase di esecuzione se provi ad accedervi. Questa è essenzialmente la stessa cosa di una chiamata [self doesNotRecognizeSelector:_cmd], ma con una digitazione molto minore.


7

In Xcode (usando clang ecc.) Mi piace usare __attribute__((unavailable(...)))per taggare le classi astratte in modo da ottenere un errore / avviso se provi ad usarlo.

Fornisce una protezione contro l'utilizzo accidentale del metodo.

Esempio

Nel @interfacetag della classe base i metodi "astratti":

- (void)myAbstractMethod:(id)param1 __attribute__((unavailable("You should always override this")));

Facendo un ulteriore passo avanti, creo una macro:

#define UnavailableMacro(msg) __attribute__((unavailable(msg)))

Questo ti permette di fare questo:

- (void)myAbstractMethod:(id)param1 UnavailableMacro(@"You should always override this");

Come ho detto, questa non è una vera protezione del compilatore, ma è buona quanto la tua conoscenza di un linguaggio che non supporta metodi astratti.


1
Non riuscivo a capirlo. Ho applicato il tuo suggerimento sulla mia classe di base -init metodo e ora Xcode non mi permette creare un'istanza della classe ereditata e un errore di compilazione si verifica non disponibili ... . Potresti spiegare di più?
entro il

Assicurati di avere un -initmetodo nella sottoclasse.
Richard Stelling,

2
È lo stesso di NS_UNAVAILABLE che attiverà un errore ogni volta che proverai a chiamare il metodo contrassegnato con tale attributo. Non vedo come possa essere usato in classe astratta.
Ben Sinclair,

7

La risposta alla domanda è sparsa nei commenti sotto le risposte già fornite. Quindi, sto solo riassumendo e semplificando qui.

Opzione 1: protocolli

Se si desidera creare una classe astratta senza implementazione utilizzare 'Protocolli'. Le classi che ereditano un protocollo sono obbligate a implementare i metodi nel protocollo.

@protocol ProtocolName
// list of methods and properties
@end

Opzione2: Modello Metodo modello

Se vuoi creare una classe astratta con un'implementazione parziale come "Template Method Pattern", questa è la soluzione. Objective-C - Pattern dei metodi template?


6

Un'altra alternativa

Basta controllare la classe nella classe Abstract e Assert o Exception, qualunque cosa tu voglia.

@implementation Orange
- (instancetype)init
{
    self = [super init];
    NSAssert([self class] != [Orange class], @"This is an abstract class");
    if (self) {
    }
    return self;
}
@end

Ciò elimina la necessità di eseguire l'override init


Sarebbe efficiente restituire zero? O ciò provocherebbe la generazione di un'eccezione / altro errore?
user2277872

Bene, questo è solo per catturare i programmatori per usare direttamente la classe, che penso faccia buon uso di un'asserzione.
bigkm,

5

(più di un suggerimento correlato)

Volevo avere un modo per far sapere al programmatore "non chiamare da figlio" e per sostituirlo completamente (nel mio caso offrire comunque alcune funzionalità predefinite per conto del genitore quando non esteso):

typedef void override_void;
typedef id override_id;

@implementation myBaseClass

// some limited default behavior (undesired by subclasses)
- (override_void) doSomething;
- (override_id) makeSomeObject;

// some internally required default behavior
- (void) doesSomethingImportant;

@end

Il vantaggio è che il programmatore VEDERÀ la "sostituzione" nella dichiarazione e saprà che non dovrebbe chiamare [super ..].

Certo, è brutto dover definire i singoli tipi di ritorno per questo, ma serve come un suggerimento visivo abbastanza buono e non puoi facilmente usare la parte "override_" in una definizione di sottoclasse.

Naturalmente una classe può ancora avere un'implementazione predefinita quando un'estensione è facoltativa. Ma come dicono le altre risposte, implementare un'eccezione di runtime quando appropriato, come per le classi astratte (virtuali).

Sarebbe bello avere suggerimenti compilatore come questo, anche suggerimenti su quando è meglio pre / post chiamare l'attrezzo del super, invece di dover scavare commenti / documentazione o ... assumere.

esempio del suggerimento


4

Se sei abituato al compilatore che rileva violazioni di istanza astratte in altre lingue, il comportamento di Objective-C è deludente.

Come linguaggio di associazione tardiva è chiaro che Objective-C non può prendere decisioni statiche sul fatto che una classe sia veramente astratta o meno (potresti aggiungere funzioni in fase di esecuzione ...), ma per i casi d'uso tipici questo sembra un difetto. Preferirei che il compilatore non fosse in grado di creare istanze di classi astratte invece di generare un errore in fase di esecuzione.

Ecco uno schema che stiamo usando per ottenere questo tipo di controllo statico usando un paio di tecniche per nascondere gli inizializzatori:

//
//  Base.h
#define UNAVAILABLE __attribute__((unavailable("Default initializer not available.")));

@protocol MyProtocol <NSObject>
-(void) dependentFunction;
@end

@interface Base : NSObject {
    @protected
    __weak id<MyProtocol> _protocolHelper; // Weak to prevent retain cycles!
}

- (instancetype) init UNAVAILABLE; // Prevent the user from calling this
- (void) doStuffUsingDependentFunction;
@end

//
//  Base.m
#import "Base.h"

// We know that Base has a hidden initializer method.
// Declare it here for readability.
@interface Base (Private)
- (instancetype)initFromDerived;
@end

@implementation Base
- (instancetype)initFromDerived {
    // It is unlikely that this becomes incorrect, but assert
    // just in case.
    NSAssert(![self isMemberOfClass:[Base class]],
             @"To be called only from derived classes!");
    self = [super init];
    return self;
}

- (void) doStuffUsingDependentFunction {
    [_protocolHelper dependentFunction]; // Use it
}
@end

//
//  Derived.h
#import "Base.h"

@interface Derived : Base
-(instancetype) initDerived; // We cannot use init here :(
@end

//
//  Derived.m
#import "Derived.h"

// We know that Base has a hidden initializer method.
// Declare it here.
@interface Base (Private)
- (instancetype) initFromDerived;
@end

// Privately inherit protocol
@interface Derived () <MyProtocol>
@end

@implementation Derived
-(instancetype) initDerived {
    self= [super initFromDerived];
    if (self) {
        self->_protocolHelper= self;
    }
    return self;
}

// Implement the missing function
-(void)dependentFunction {
}
@end

3

Probabilmente questo tipo di situazioni dovrebbe accadere solo in fase di sviluppo, quindi potrebbe funzionare:

- (id)myMethodWithVar:(id)var {
   NSAssert(NO, @"You most override myMethodWithVar:");
   return nil;
}

3

Puoi usare un metodo proposto da @Yar (con alcune modifiche):

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()

Qui riceverai un messaggio come:

<Date> ProjectName[7921:1967092] <Class where method not implemented> - method not implemented
<Date> ProjectName[7921:1967092] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[<Base class (if inherited or same if not> <Method name>] must be overridden in a subclass/category'

O affermazione:

NSAssert(![self respondsToSelector:@selector(<MethodName>)], @"Not implemented");

In questo caso otterrai:

<Date> ProjectName[7926:1967491] *** Assertion failure in -[<Class Name> <Method name>], /Users/kirill/Documents/Projects/root/<ProjectName> Services/Classes/ViewControllers/YourClass:53

Inoltre puoi usare protocolli e altre soluzioni, ma questa è una delle più semplici.


2

Il cacao non fornisce nulla chiamato astratto. Possiamo creare un abstract di classe che viene verificato solo in fase di esecuzione e in fase di compilazione non viene verificato.


1

Di solito disabilito solo il metodo init in una classe che voglio astrarre:

- (instancetype)__unavailable init; // This is an abstract class.

Ciò genererà un errore al momento della compilazione ogni volta che si chiama init su quella classe. Quindi uso i metodi di classe per tutto il resto.

Objective-C non ha un modo integrato per dichiarare le classi astratte.


Ma ricevo un errore quando chiamo [super init] dalla classe derivata di quella classe astratta. Come risolverlo?
Sahil Doshi,

@SahilDoshi Suppongo che questo sia il lato negativo dell'utilizzo di questo metodo. Lo uso quando non voglio consentire un'istanza di una classe e nulla eredita da quella classe.
Mahoukov,

si l'ho capito. ma la tua soluzione ti aiuterà a creare qualcosa come la classe singleton in cui non vogliamo che nessuno chiami init. secondo la mia conoscenza in caso di classe astratta, una classe la erediterà. altrimenti qual è l'uso della classe astratta.
Sahil Doshi,

0

Cambiando un po 'quello che @redfood ha suggerito applicando il commento di @ dotToString, in realtà hai la soluzione adottata da IGListKit di Instagram .

  1. Creare un protocollo per tutti i metodi che non hanno senso essere definiti nella classe di base (astratta), cioè hanno bisogno di implementazioni specifiche nei bambini.
  2. Creare una classe di base (astratta) che non implementa questo protocollo. È possibile aggiungere a questa classe qualsiasi altro metodo che abbia senso per avere un'implementazione comune.
  3. Ovunque nel progetto, se un figlio di AbstractClassdeve essere immesso o emesso da un metodo, digita come AbstractClass<Protocol>invece.

Poiché AbstractClassnon implementa Protocol, l'unico modo per avere AbstractClass<Protocol>un'istanza è la sottoclasse. Poiché da AbstractClasssolo non può essere utilizzato in nessun punto del progetto, diventa astratto.

Naturalmente, ciò non impedisce agli sviluppatori sconsigliati di aggiungere nuovi metodi riferendosi semplicemente a AbstractClass, il che finirebbe per consentire un'istanza della classe (non più) astratta.

Esempio reale: IGListKit ha una classe base IGListSectionControllerche non implementa il protocollo IGListSectionType, tuttavia ogni metodo che richiede un'istanza di quella classe richiede effettivamente il tipo IGListSectionController<IGListSectionType>. Pertanto non c'è modo di usare un oggetto di tipo IGListSectionControllerper qualcosa di utile nel loro framework.


0

In effetti, Objective-C non ha classi astratte, ma è possibile utilizzare i protocolli per ottenere lo stesso effetto. Ecco l'esempio:

CustomProtocol.h

#import <Foundation/Foundation.h>

@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end

TestProtocol.h

#import <Foundation/Foundation.h>
#import "CustomProtocol.h"

@interface TestProtocol : NSObject <CustomProtocol>

@end

TestProtocol.m

#import "TestProtocol.h"

@implementation TestProtocol

- (void)methodA
{
  NSLog(@"methodA...");
}

- (void)methodB
{
  NSLog(@"methodB...");
}
@end

0

Un semplice esempio di creazione di una classe astratta

// Declare a protocol
@protocol AbcProtocol <NSObject>

-(void)fnOne;
-(void)fnTwo;

@optional

-(void)fnThree;

@end

// Abstract class
@interface AbstractAbc : NSObject<AbcProtocol>

@end

@implementation AbstractAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

-(void)fnOne{
// Code
}

-(void)fnTwo{
// Code
}

@end

// Implementation class
@interface ImpAbc : AbstractAbc

@end

@implementation ImpAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

// You may override it    
-(void)fnOne{
// Code
}
// You may override it
-(void)fnTwo{
// Code
}

-(void)fnThree{
// Code
}

@end

-3

Non puoi semplicemente creare un delegato?

Un delegato è come una classe base astratta, nel senso che dici quali funzioni devono essere definite, ma in realtà non le definisci.

Quindi, ogni volta che si implementa il proprio delegato (ovvero la classe astratta), si viene avvisati dal compilatore di quali funzioni opzionali e obbligatorie sono necessarie per definire il comportamento.

Mi sembra una classe base astratta.

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.