Domanda

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?

È stato utile?

Soluzione

In genere, le classi Objective-C sono astratte solo per convenzione & # 8212; se l'autore documenta una classe come astratta, semplicemente non usarla senza sottoclassarla. Tuttavia, non esiste un'applicazione di tempo di 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.

Altri suggerimenti

No, non c'è modo di 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.

Ad esempio:

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

Puoi anche farlo per init.

Sto solo cercando la risposta di @Barry Wark sopra (e aggiornandola per iOS 4.3) e lasciandola per 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 avrò studiato il contrario. Penso che sia più corretto usare NSInvalidArgumentException (piuttosto che NSInternalInconsistencyException) poiché è quello che il sistema di runtime lancia in risposta alla doesNotRecognizeSelector chiamata (vedi NSObject documenti).

La soluzione che mi è venuta in mente è:

  1. Crea un protocollo per tutto quello che vuoi nel tuo " abstract " Classe
  2. Crea una classe base (o forse chiamala astratta) che implementa il protocollo. Per tutti i metodi che vuoi & Quot; abstract & Quot; implementarli 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.

Dalla Omni Group mailing list :

Objective-C non ha il costrutto di compilatore astratto come Java su questa volta.

Quindi tutto ciò che fai è definire la classe astratta come qualsiasi altra classe normale e implementare metodi stub per i metodi astratti che sono entrambi vuoto o segnala non supporto per il selettore. Ad esempio ...

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

Faccio anche quanto segue per impedire l'inizializzazione dell'abstract classe tramite l'inizializzatore predefinito.

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

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.

Questo thread è un po 'vecchio e la maggior parte di ciò che voglio condividere è già qui.

Tuttavia, il mio metodo preferito non è menzionato, e AFAIK lì & # 8217; s non c'è supporto nativo nell'attuale Clang, quindi qui vado & # 8230;

Innanzitutto, e soprattutto (come altri hanno già sottolineato) le classi astratte sono qualcosa di molto raro in Objective-C & # 8212; usiamo invece la composizione (a volte attraverso la delega). Questo è probabilmente il motivo per cui una tale funzione non esiste & # 8217; t esiste già nel linguaggio / compilatore & # 8212; a parte @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 è & # 8217; non è adatta a risolvere il tuo problema, qui & # 8217 ; s come I lo faccio:

  1. Implementa tutti i metodi astratti nella classe base.
  2. Effettua l'implementazione [self doesNotRecognizeSelector:_cmd]; & # 8230;
  3. & # 8230; seguito da __builtin_unreachable(); per silenziare l'avvertimento & # 8217; otterrai metodi non nulli, che indicano & # 8220; il controllo ha raggiunto la fine della funzione non nulla senza un ritorno & # 8221 ;.
  4. Combina i passaggi 2. e 3. in una macro oppure annota -[NSObject doesNotRecognizeSelector:] utilizzando __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 tuo progetto & # 8217; s PCH.

Preferisco personalmente la versione macro in quanto mi consente di ridurre il più possibile la piastra di cottura.

Eccolo:

// 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 fare lobby su Clang team per fornire un attributo del 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. Ottiene & # 8217; s il lavoro svolto in modo efficiente e in qualche modo conveniente.
  2. It & # 8217; è abbastanza facile da capire. (Va bene, che __builtin_unreachable() può sorprendere le persone, ma & # 8217; è abbastanza facile anche da capire.)
  3. Non può essere rimosso nelle build di rilascio senza generare altri avvisi del compilatore o errori & # 8212; a differenza di un approccio che & # 8217; s si basa su una delle macro di asserzione.

Quest'ultimo punto necessita di qualche spiegazione, immagino:

Alcune (la maggior parte?) persone rimuovono le asserzioni nelle build di rilascio. (Non sono d'accordo con quell'abitudine, ma quella & # 8217; s un'altra storia & # 8230;) Non riuscendo a implementare un metodo richiesto & # 8212; comunque & # 8212; è cattivo , terribile , sbagliato e sostanzialmente 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 sistemi diagnostici senza generare nuovi sistemi diagnostici sarebbe assolutamente inaccettabile.

È & # 8217; è abbastanza grave che non è possibile ottenere una corretta diagnostica in fase di compilazione per tali errori del programmatore e si deve ricorrere alla scoperta in fase di esecuzione per questi, ma se si può intonarci su di esso nel rilascio build, perché provare ad avere una classe astratta in primo luogo?

Anche

l'uso di @property e @dynamic potrebbe funzionare. Se dichiari una proprietà dinamica e non fornisci un'implementazione del metodo di corrispondenza, tutto verrà comunque compilato senza avvisi e otterrai un errore unrecognized selector in fase di esecuzione se provi ad accedervi. Questa è essenzialmente la stessa cosa di chiamare [self doesNotRecognizeSelector:_cmd], ma con una digitazione molto minore.

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

Fornisce una protezione contro l'utilizzo accidentale del metodo.

Esempio

Nella classe base @interface tagga " abstract " metodi:

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

Procedendo ulteriormente, 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 i metodi astratti.

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

Opzione 1: protocolli

Se vuoi creare una classe astratta senza implementazione usa '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 si desidera creare una classe astratta con implementazione parziale come " Modello Metodo Pattern " allora questa è la soluzione. Objective-C - Pattern metodi modello?

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 sostituire init

(più di un suggerimento correlato)

Volevo avere un modo per far sapere al programmatore " non chiamare dal figlio " e per sostituire 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À il " sovrascrive " nella dichiarazione e saprà che non dovrebbero chiamare [super ..].

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

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

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

esempio del suggerimento

Se sei abituato al compilatore a rilevare 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 lacuna. Preferirei che il compilatore appiattisse le istanze evitate delle 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

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;
}

Puoi utilizzare 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 asserzione:

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.

Cocoa doesn & # 8217; t fornire qualsiasi cosa chiamata abstract. Possiamo creare un abstract di classe che viene verificato solo in fase di esecuzione e in fase di compilazione non viene verificato.

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

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

Questo genererà un errore in fase di compilazione ogni volta che chiamate 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.

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

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

Poiché Protocol non implementa IGListSectionController, l'unico modo per avere un'istanza IGListSectionType è la sottoclasse. Poiché IGListSectionController<IGListSectionType> da solo non può essere utilizzato in nessuna parte del progetto, diventa astratto.

Ovviamente, ciò non impedisce agli sviluppatori sconsigliati di aggiungere nuovi metodi facendo riferimento semplicemente a <=>, il che finirebbe per consentire un'istanza della classe (non più) astratta.

Esempio reale: IGListKit ha una classe base <=> che non implementa il protocollo < =>, tuttavia ogni metodo che richiede un'istanza di quella classe, in realtà richiede il tipo <=>. Pertanto non c'è modo di usare un oggetto di tipo <=> per qualsiasi cosa utile nel loro framework.

In effetti, Objective-C non ha classi astratte, ma puoi usare 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

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

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 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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top