Erstellen einer abstrakten Klasse in Objective-C

507

Ich bin ursprünglich ein Java-Programmierer, der jetzt mit Objective-C arbeitet. Ich möchte eine abstrakte Klasse erstellen, aber das scheint in Objective-C nicht möglich zu sein. Ist das möglich?

Wenn nicht, wie nahe kann ich einer abstrakten Klasse in Objective-C kommen?

Jonathan Arbogast
quelle
18
Die Antworten unten sind großartig. Ich finde, dass das Problem der abstrakten Klassen tangential mit privaten Methoden zusammenhängt - beide sind Methoden, um die Möglichkeiten des Client-Codes einzuschränken, und beide existieren in Objective-C nicht. Ich denke, es hilft zu verstehen, dass sich die Mentalität der Sprache selbst grundlegend von Java unterscheidet. Siehe meine Antwort auf: stackoverflow.com/questions/1020070/#1020330
Quinn Taylor
Vielen Dank für die Informationen zur Mentalität der Objective-C-Community im Gegensatz zu anderen Sprachen. Das löst wirklich eine Reihe verwandter Fragen, die ich hatte (zum Beispiel, warum es keinen einfachen Mechanismus für private Methoden usw. gibt).
Jonathan Arbogast
1
Schauen Sie sich also die CocoaDev-Site an, die einen Java-Vergleich bietet. cocoadev.com/index.pl?AbstractSuperClass
2
Obwohl Barry es als nachträglichen Gedanken erwähnt (verzeihen Sie mir, wenn ich es falsch lese), denke ich, dass Sie in Ziel C nach einem Protokoll suchen . Siehe zum Beispiel Was ist ein Protokoll? .
JWW

Antworten:

633

In der Regel sind Objective-C-Klassen nur gemäß Konvention abstrakt. Wenn der Autor eine Klasse als abstrakt dokumentiert, verwenden Sie sie nur nicht, ohne sie zu unterklassifizieren. Es gibt jedoch keine Durchsetzung zur Kompilierungszeit, die die Instanziierung einer abstrakten Klasse verhindert. Tatsächlich hindert nichts einen Benutzer daran, Implementierungen abstrakter Methoden über eine Kategorie (dh zur Laufzeit) bereitzustellen. Sie können einen Benutzer zwingen, zumindest bestimmte Methoden zu überschreiben, indem Sie eine Ausnahme in der Methodenimplementierung in Ihrer abstrakten Klasse auslösen:

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

Wenn Ihre Methode einen Wert zurückgibt, ist die Verwendung etwas einfacher

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

In diesem Fall müssen Sie der Methode keine return-Anweisung hinzufügen.

Wenn die abstrakte Klasse wirklich eine Schnittstelle ist (dh keine konkreten Methodenimplementierungen aufweist), ist die Verwendung eines Objective-C-Protokolls die geeignetere Option.

Barry Wark
quelle
18
Ich denke, der am besten geeignete Teil der Antwort war zu erwähnen, dass Sie stattdessen @protocol verwenden könnten, um nur Methoden zu definieren.
Chad Stewart
13
Zur Verdeutlichung: Sie können Methoden in einer Definition deklarieren@protocol , aber Sie können die Methoden dort nicht definieren.
Richard
Mit einer Kategoriemethode unter NSException + (instancetype)exceptionForCallingAbstractMethod:(SEL)selectorfunktioniert dies sehr gut.
Patrick Pijnappel
Das hat für mich geklappt, da meiner Meinung nach für andere Entwickler offensichtlicher ist, dass dies ein gewünschtes Verhalten ist doesNotRecognizeSelector.
Chris
Verwenden Sie Protokolle (für vollständig abstrakte Klassen) oder Vorlagenmethodenmuster, bei denen die abstrakte Klasse eine teilweise Implementierung / Flusslogik aufweist, wie hier angegeben . Stackoverflow.com/questions/8146439/… . Siehe meine Antwort unten.
Hadaytullah
267

Nein, es gibt keine Möglichkeit, eine abstrakte Klasse in Objective-C zu erstellen.

Sie können eine abstrakte Klasse verspotten, indem Sie den Methoden- / Selektor-Aufruf von doesNotRecognizeSelector ausführen: und daher eine Ausnahme auslösen, die die Klasse unbrauchbar macht.

Zum Beispiel:

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

Sie können dies auch für init tun.

Grouchal
quelle
5
@Chuck, ich habe nicht abgelehnt, aber die NSObjectReferenz schlägt vor, dies zu verwenden, wenn Sie KEINE Methode erben möchten, um das Überschreiben einer Methode nicht zu erzwingen. Obwohl das vielleicht dasselbe ist, vielleicht :)
Dan Rosenstark
Warum sollten Sie in diesem Fall nicht einfach ein Protokoll verwenden? Für mich ist das nur für Methodenstubs gut zu wissen, aber nicht für eine ganze abstrakte Klasse. Der Anwendungsfall hierfür scheint begrenzt.
Lewisuez
Ich habe es nicht abgelehnt, aber der Vorschlag, dass Sie eine Ausnahme in der init-Methode auslösen sollten, ist der wahrscheinlichste Grund dafür. Das gebräuchlichste Format für eine Unterklasse startet ihre eigene Init-Methode durch Aufrufen von self = [super init] - wodurch gehorsam eine Ausnahme ausgelöst wird. Dieses Format funktioniert für die meisten Methoden gut, aber ich würde es niemals in irgendetwas tun, in dem die Unterklasse es als Super-Implementierung bezeichnen könnte.
Xono
5
Sie können in Objective-C absolut abstrakte Klassen erstellen, und das ist durchaus üblich. Es gibt eine Reihe von Apple Framework-Klassen, die dies tun. Apples abstrakte Klassen lösen bestimmte Ausnahmen aus (NSInvalidArgumentException), häufig durch Aufrufen von NSInvalidAbstractInvocation (). Das Aufrufen einer abstrakten Methode ist ein Programmierfehler, weshalb eine Ausnahme ausgelöst wird. Abstrakte Fabriken werden üblicherweise als Klassencluster implementiert.
quellish
2
@quellish: Wie Sie sagten: Das Aufrufen einer abstrakten Methode ist ein Programmierfehler. Es sollte als solches behandelt werden, anstatt sich auf die Laufzeitfehlerberichterstattung (NSException) zu verlassen. Dies ist meiner Meinung nach das größte Problem für Entwickler aus anderen Sprachen, in denen abstrakt bedeutet, dass ein Objekt dieses Typs in Obj-C nicht instanziiert werden kann. Dies bedeutet, dass zur Laufzeit Fehler auftreten, wenn Sie diese Klasse instanziieren.
Cross_
60

Ich habe gerade die Antwort von @Barry Wark oben gelesen (und für iOS 4.3 aktualisiert) und dies für meine eigene Referenz belassen:

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

dann können Sie dies in Ihren Methoden verwenden

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



Anmerkungen: Ich bin mir nicht sicher, ob es eine gute Idee ist, ein Makro wie eine C-Funktion aussehen zu lassen oder nicht, aber ich werde es behalten, bis ich das Gegenteil gelernt habe. Ich denke, es ist korrekter zu verwenden NSInvalidArgumentException(anstatt NSInternalInconsistencyException), da das Laufzeitsystem doesNotRecognizeSelectordies als Reaktion auf den Aufruf auslöst (siehe NSObjectDokumente).

Dan Rosenstark
quelle
1
Sicher, @TomA, ich hoffe, es gibt Ihnen Ideen für anderen Code, den Sie makro können. Mein am häufigsten verwendetes Makro ist eine einfache Referenz auf einen Singleton: Der Code sagt, universe.thingaber er erweitert sich auf [Universe universe].thing. Großer Spaß, Tausende von Buchstaben des Codes speichern ...
Dan Rosenstark
Großartig. Es wurde jedoch ein wenig geändert : #define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil].
Noamtm
1
@Yar: Das glaube ich nicht. Wir verwenden __PRETTY_FUNCTION__überall über ein DLog-Makro (...), wie hier vorgeschlagen: stackoverflow.com/a/969291/38557 .
Noamtm
1
für weitere Details -#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
gbk
1
Wenn Sie eine Basisklasse hinzufügen und diese Klasse dann beispielsweise 10 Mal erben und vergessen haben, diese in einer der Klassen zu implementieren, erhalten Sie eine Nachricht mit dem Namen der nicht geerbten Basisklasse wie Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'In dem Fall, dass ich vorgeschlagen habe, auch zu HomeViewController - method not implementederfahren , wo HomeViewController von Base geerbt wurde - dies wird mehr Informationen geben
gbk
42

Die Lösung, die ich gefunden habe, ist:

  1. Erstellen Sie ein Protokoll für alles, was Sie in Ihrer "abstrakten" Klasse wollen
  2. Erstellen Sie eine Basisklasse (oder nennen Sie sie abstrakt), die das Protokoll implementiert. Implementieren Sie alle Methoden, die "abstrakt" sein sollen, in die .m-Datei, nicht jedoch in die .h-Datei.
  3. Lassen Sie Ihre untergeordnete Klasse von der Basisklasse erben UND implementieren Sie das Protokoll.

Auf diese Weise gibt der Compiler eine Warnung für jede Methode im Protokoll aus, die nicht von Ihrer untergeordneten Klasse implementiert wurde.

Es ist nicht so prägnant wie in Java, aber Sie erhalten die gewünschte Compiler-Warnung.

Redfood
quelle
2
+1 Dies ist wirklich die Lösung, die einer abstrakten Klasse in Java am nächsten kommt. Ich habe diesen Ansatz selbst verwendet und er funktioniert hervorragend. Es ist sogar erlaubt, das Protokoll genauso zu benennen wie die Basisklasse (genau wie Apple es getan hat NSObject). Wenn Sie dann das Protokoll und die Basisklassendeklaration in dieselbe Headerdatei einfügen, ist sie von einer abstrakten Klasse kaum zu unterscheiden.
CodingFriend1
Ah, aber meine abstrakte Klasse implementiert einen Teil eines Protokolls, der Rest wird von Unterklassen implementiert.
Roger CS Wernersson
1
Sie könnten nur die untergeordneten Klassen das Protokoll implementieren lassen und die Methoden der Oberklasse alle zusammen auslassen, anstatt leer zu sein. haben dann Eigenschaften vom Typ Superklasse <MyProtocol>. Für zusätzliche Flexibilität können Sie Ihren Methoden in Ihrem Protokoll auch @optional voranstellen.
dotToString
Dies scheint in Xcode 5.0.2 nicht zu funktionieren. Es wird nur eine Warnung für die "abstrakte" Klasse generiert. Wenn Sie die "abstrakte" Klasse nicht erweitern, wird die richtige Compiler-Warnung generiert, aber Sie können die Methoden offensichtlich nicht erben.
Topher Fangio
Ich bevorzuge diese Lösung, mag sie aber nicht, sie ist wirklich keine gute Codestruktur im Projekt. // sollte dies als Basistyp für Unterklassen verwenden. typedef BaseClass <BaseClassProtocol> BASECLASS; Es ist nur eine Wochenregel, ich mag es nicht.
Itachi
35

Aus der Omni Group-Mailingliste :

Objective-C verfügt derzeit nicht über das abstrakte Compiler-Konstrukt wie Java.

Sie definieren also nur die abstrakte Klasse wie jede andere normale Klasse und implementieren Methodenstubs für die abstrakten Methoden, die entweder leer sind oder keine Unterstützung für den Selektor melden. Zum Beispiel...

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

Ich mache auch Folgendes, um die Initialisierung der abstrakten Klasse über den Standardinitialisierer zu verhindern.

- (id)init
{
     [self doesNotRecognizeSelector:_cmd];
     [self release];
     return nil;
}
Peter Mortensen
quelle
1
Ich hatte nicht daran gedacht, -doesNotRecognizeSelector zu verwenden: und ich mag diesen Ansatz in gewisser Weise. Kennt jemand eine Möglichkeit, den Compiler dazu zu bringen, eine Warnung für eine "abstrakte" Methode auszugeben, die entweder auf diese Weise oder durch Auslösen einer Ausnahme erstellt wurde? Das wäre großartig ...
Quinn Taylor
26
Der Ansatz doesNotRecognizeSelector verhindert das von Apple vorgeschlagene Muster self = [super init].
Dlinsin
1
@david: Ich bin mir ziemlich sicher, dass es darum geht, so schnell wie möglich eine Ausnahme auszulösen. Idealerweise sollte es zur Kompilierungszeit sein, aber da dies nicht möglich ist, haben sie sich für eine Laufzeitausnahme entschieden. Dies ist vergleichbar mit einem Assertionsfehler, der überhaupt nicht im Produktionscode ausgelöst werden sollte. Tatsächlich könnte eine Behauptung (falsch) tatsächlich besser sein, da klarer ist, dass dieser Code niemals ausgeführt werden sollte. Der Benutzer kann nichts tun, um das Problem zu beheben. Der Entwickler muss es beheben. Daher klingt es nach einer guten Idee, hier eine Ausnahme oder einen Assertionsfehler auszulösen.
Sinnvoll
2
Ich denke nicht, dass dies eine praktikable Lösung ist, da Unterklassen durchaus legitime Aufrufe an [super init] haben können.
Raffi Khatchadourian
@dlinsin: Genau das möchten Sie, wenn die abstrakte Klasse nicht über init initialisiert werden soll. Die Unterklassen wissen vermutlich, welche Supermethode aufgerufen werden soll, aber dies stoppt den unachtsamen Aufruf über "new" oder "alloc / init".
Gnasher729
21

Anstatt zu versuchen, eine abstrakte Basisklasse zu erstellen, sollten Sie ein Protokoll verwenden (ähnlich einer Java-Schnittstelle). Auf diese Weise können Sie eine Reihe von Methoden definieren und dann alle Objekte akzeptieren, die dem Protokoll entsprechen, und die Methoden implementieren. Zum Beispiel kann ich ein Operationsprotokoll definieren und dann eine Funktion wie diese haben:

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

Wobei op ein beliebiges Objekt sein kann, das das Operationsprotokoll implementiert.

Wenn Ihre abstrakte Basisklasse mehr als nur Methoden definieren muss, können Sie eine reguläre Objective-C-Klasse erstellen und deren Instanziierung verhindern. Überschreiben Sie einfach die Funktion - (id) init und lassen Sie sie nil zurückgeben oder assert (false). Es ist keine sehr saubere Lösung, aber da Objective-C vollständig dynamisch ist, gibt es wirklich kein direktes Äquivalent zu einer abstrakten Basisklasse.

Ben Gotow
quelle
Für mich scheint dies der geeignete Weg für Fälle zu sein, in denen Sie eine abstrakte Klasse verwenden würden, zumindest wenn dies wirklich "Schnittstelle" bedeuten soll (wie in C ++). Gibt es irgendwelche versteckten Nachteile dieses Ansatzes?
Febeling
1
@febeling, abstrakte Klassen - zumindest in Java - sind nicht nur Schnittstellen. Sie definieren auch einige (oder die meisten) Verhaltensweisen. Dieser Ansatz könnte jedoch in einigen Fällen gut sein.
Dan Rosenstark
Ich benötige eine Basisklasse, um bestimmte Funktionen zu implementieren, die alle meine Unterklassen gemeinsam nutzen (Entfernen von Duplikaten), aber ich benötige auch ein Protokoll für andere Methoden, die die Basisklasse nicht verarbeiten sollte (den abstrakten Teil). Ich muss also beides verwenden, und dort kann es schwierig werden, sicherzustellen, dass Ihre Unterklassen sich selbst ordnungsgemäß implementieren.
LightningStryk
19

Dieser Thread ist ziemlich alt und das meiste, was ich teilen möchte, ist bereits hier.

Meine Lieblingsmethode wird jedoch nicht erwähnt, und AFAIK gibt es im aktuellen Clang keine native Unterstützung, also los geht's…

In erster Linie (wie andere bereits betont haben) sind abstrakte Klassen in Objective-C etwas sehr Ungewöhnliches - wir verwenden stattdessen normalerweise Komposition (manchmal durch Delegation). Dies ist wahrscheinlich der Grund, warum eine solche Funktion in der Sprache / im Compiler noch nicht vorhanden ist - abgesehen von @dynamicEigenschaften, die IIRC in ObjC 2.0 zur Einführung von CoreData hinzugefügt wurden.

Angesichts der Tatsache, dass Sie (nach sorgfältiger Beurteilung Ihrer Situation!) Zu dem Schluss gekommen sind, dass die Delegation (oder die Zusammensetzung im Allgemeinen) nicht gut zur Lösung Ihres Problems geeignet ist, gehe ich folgendermaßen vor :

  1. Implementieren Sie jede abstrakte Methode in der Basisklasse.
  2. Machen Sie diese Implementierung [self doesNotRecognizeSelector:_cmd];...
  3. … Gefolgt von __builtin_unreachable();der Stummschaltung der Warnung, die Sie für nicht leere Methoden erhalten, und der Meldung, dass die Kontrolle das Ende der nicht leeren Funktion ohne Rückkehr erreicht hat.
  4. Kombinieren Sie entweder die Schritte 2. und 3. in einem Makro oder kommentieren Sie die -[NSObject doesNotRecognizeSelector:]Verwendung __attribute__((__noreturn__))in einer Kategorie ohne Implementierung , um die ursprüngliche Implementierung dieser Methode nicht zu ersetzen, und fügen Sie den Header für diese Kategorie in den PCH Ihres Projekts ein.

Ich persönlich bevorzuge die Makroversion, da ich so die Kesselplatte so weit wie möglich reduzieren kann.

Hier ist es:

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

Wie Sie sehen können, bietet das Makro die vollständige Implementierung der abstrakten Methoden, wodurch die erforderliche Menge an Boilerplate auf ein absolutes Minimum reduziert wird.

Eine noch bessere Option wäre es, das Clang-Team dazu zu bewegen, über Feature-Anfragen ein Compiler-Attribut für diesen Fall bereitzustellen. (Besser, da dies auch die Diagnose zur Kompilierungszeit für Szenarien ermöglichen würde, in denen Sie eine Unterklasse haben, z. B. NSIncrementalStore.)

Warum ich diese Methode wähle

  1. Es erledigt die Arbeit effizient und etwas bequemer.
  2. Es ist ziemlich leicht zu verstehen. (Okay, das __builtin_unreachable()mag die Leute überraschen, aber es ist auch leicht zu verstehen.)
  3. In Release-Builds kann es nicht entfernt werden, ohne dass andere Compiler-Warnungen oder -Fehler generiert werden - im Gegensatz zu einem Ansatz, der auf einem der Assertion-Makros basiert.

Dieser letzte Punkt bedarf einer Erklärung, denke ich:

Einige (die meisten?) Leute entfernen Behauptungen in Release-Builds. (Ich bin mit dieser Angewohnheit nicht einverstanden, aber das ist eine andere Geschichte…) Die Nichtimplementierung einer erforderlichen Methode ist jedoch schlecht , schrecklich , falsch und im Grunde das Ende des Universums für Ihr Programm. Ihr Programm kann in dieser Hinsicht nicht richtig funktionieren, da es undefiniert ist und undefiniertes Verhalten das Schlimmste ist, was es je gab. Daher wäre es völlig inakzeptabel, diese Diagnosen entfernen zu können, ohne neue Diagnosen zu generieren.

Es ist schon schlimm genug, dass Sie für solche Programmiererfehler keine ordnungsgemäße Diagnose zur Kompilierungszeit erhalten und für diese Fehler zur Laufzeit auf diese zurückgreifen müssen. Wenn Sie sie jedoch in Release-Builds verputzen können, warum sollten Sie versuchen, eine abstrakte Klasse in der zu haben? erster Platz?

Danyowdee
quelle
Dies ist meine neue Lieblingslösung - das __builtin_unreachable();Juwel macht diese Arbeit perfekt. Das Makro macht es selbstdokumentierend und das Verhalten stimmt mit dem überein, was passiert, wenn Sie eine fehlende Methode für ein Objekt aufrufen.
Alex MDC
12

Verwenden @propertyund @dynamickönnte auch funktionieren. Wenn Sie eine dynamische Eigenschaft deklarieren und keine übereinstimmende Methodenimplementierung angeben, wird weiterhin alles ohne Warnungen kompiliert, und unrecognized selectorzur Laufzeit wird eine Fehlermeldung angezeigt, wenn Sie versuchen, darauf zuzugreifen. Dies ist im Wesentlichen dasselbe wie das Anrufen [self doesNotRecognizeSelector:_cmd], jedoch mit weitaus weniger Eingabe.

Cameron Spickert
quelle
7

In Xcode (mit clang usw.) möchte ich __attribute__((unavailable(...)))die abstrakten Klassen mit Tags versehen, damit Sie einen Fehler / eine Warnung erhalten, wenn Sie versuchen, sie zu verwenden.

Es bietet einen gewissen Schutz gegen versehentliches Verwenden der Methode.

Beispiel

Im Basisklassen- @interfaceTag die "abstrakten" Methoden:

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

Wenn ich noch einen Schritt weiter gehe, erstelle ich ein Makro:

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

So können Sie Folgendes tun:

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

Wie ich bereits sagte, ist dies kein wirklicher Compiler-Schutz, aber es ist ungefähr so ​​gut, wie Sie es in einer Sprache lernen werden, die keine abstrakten Methoden unterstützt.

Richard Stelling
quelle
1
Ich konnte es nicht bekommen. Ich habe Ihren Vorschlag auf meine Methode der Basisklasse -init angewendet , und jetzt erlaubt mir Xcode nicht, eine Instanz der geerbten Klasse zu erstellen, und ein Fehler bei der Kompilierung tritt nicht auf . Würden Sie bitte mehr erklären?
Anonim
Stellen Sie sicher, dass -initIhre Unterklasse eine Methode enthält.
Richard Stelling
2
Es ist dasselbe wie NS_UNAVAILABLE, das jedes Mal einen Fehler auslöst, wenn Sie versuchen, die mit einem solchen Attribut gekennzeichnete Methode aufzurufen. Ich sehe nicht, wie es für abstrakte Klassen verwendet werden kann.
Ben Sinclair
7

Die Antwort auf die Frage ist in den Kommentaren unter den bereits gegebenen Antworten verstreut. Ich fasse hier also nur zusammen und vereinfache.

Option 1: Protokolle

Wenn Sie eine abstrakte Klasse ohne Implementierung erstellen möchten, verwenden Sie 'Protokolle'. Die Klassen, die ein Protokoll erben, sind verpflichtet, die Methoden im Protokoll zu implementieren.

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

Option 2: Muster der Vorlagenmethode

Wenn Sie eine abstrakte Klasse mit teilweiser Implementierung wie "Template Method Pattern" erstellen möchten, ist dies die Lösung. Objective-C - Muster der Vorlagenmethoden?

Hadaytullah
quelle
6

Eine andere Alternative

Überprüfen Sie einfach die Klasse in der Abstract-Klasse und Assert or Exception, was auch immer Sie möchten.

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

Dadurch entfällt die Notwendigkeit des Überschreibens init

bigkm
quelle
Wäre es effizient, einfach Null zurückzugeben? Oder würde dies dazu führen, dass eine Ausnahme / ein anderer Fehler ausgelöst wird?
user2277872
Nun, dies ist nur, um Programmierer für die direkte Verwendung der Klasse zu fangen, was meiner Meinung nach eine gute Verwendung einer Behauptung darstellt.
Bigkm
5

(eher ein verwandter Vorschlag)

Ich wollte eine Möglichkeit haben, den Programmierer über "Nicht vom Kind anrufen" zu informieren und vollständig zu überschreiben (in meinem Fall bieten wir immer noch einige Standardfunktionen im Namen des Elternteils an, wenn diese nicht erweitert werden):

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

Der Vorteil ist, dass der Programmierer die "Überschreibung" in der Deklaration sieht und weiß, dass er nicht aufrufen sollte [super ..].

Zugegeben, es ist hässlich, dafür einzelne Rückgabetypen definieren zu müssen, aber es dient als ausreichend guter visueller Hinweis, und Sie können den Teil "override_" in einer Unterklassendefinition problemlos nicht verwenden.

Natürlich kann eine Klasse immer noch eine Standardimplementierung haben, wenn eine Erweiterung optional ist. Implementieren Sie jedoch, wie in den anderen Antworten angegeben, gegebenenfalls eine Laufzeitausnahme, z. B. für abstrakte (virtuelle) Klassen.

Es wäre schön, Compiler-Hinweise wie diesen eingebaut zu haben, sogar Hinweise, wann es am besten ist, das Gerät des Super vor / nach dem Aufruf aufzurufen, anstatt Kommentare / Dokumentationen durchforsten zu müssen oder ... anzunehmen.

Beispiel für den Hinweis

Ivan Dossev
quelle
4

Wenn Sie es gewohnt sind, dass der Compiler Verstöße gegen abstrakte Instanziierungen in anderen Sprachen abfängt, ist das Objective-C-Verhalten enttäuschend.

Als späte Bindungssprache ist es klar, dass Objective-C keine statischen Entscheidungen darüber treffen kann, ob eine Klasse wirklich abstrakt ist oder nicht (Sie können zur Laufzeit Funktionen hinzufügen ...), aber für typische Anwendungsfälle scheint dies ein Mangel zu sein. Ich würde es vorziehen, wenn der Compiler die Instanziierung abstrakter Klassen pauschal verhindert, anstatt zur Laufzeit einen Fehler auszulösen.

Hier ist ein Muster, das wir verwenden, um diese Art der statischen Überprüfung mithilfe einiger Techniken zum Ausblenden von Initialisierern durchzuführen:

//
//  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
Kreuz_
quelle
3

Wahrscheinlich sollten solche Situationen nur zur Entwicklungszeit auftreten, daher könnte dies funktionieren:

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

Sie können eine von @Yar vorgeschlagene Methode verwenden (mit einigen Änderungen):

#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()

Hier erhalten Sie eine Nachricht wie:

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

Oder Behauptung:

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

In diesem Fall erhalten Sie:

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

Sie können auch Protokolle und andere Lösungen verwenden - dies ist jedoch eine der einfachsten.

gbk
quelle
2

Kakao bietet nichts, was man abstrakt nennt. Wir können eine Klassenzusammenfassung erstellen, die nur zur Laufzeit überprüft wird, und zur Kompilierungszeit wird dies nicht überprüft.

Hussain Shabbir
quelle
1

Normalerweise deaktiviere ich nur die init-Methode in einer Klasse, die ich abstrahieren möchte:

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

Dies generiert beim Kompilieren einen Fehler, wenn Sie init für diese Klasse aufrufen. Ich benutze dann Klassenmethoden für alles andere.

In Objective-C ist keine Möglichkeit zum Deklarieren abstrakter Klassen integriert.

Mahoukov
quelle
Aber ich erhalte eine Fehlermeldung, wenn ich [super init] von einer abgeleiteten Klasse dieser abstrakten Klasse aufrufe. Wie kann man das lösen?
Sahil Doshi
@SahilDoshi Ich nehme an, das ist der Nachteil dieser Methode. Ich benutze es, wenn ich nicht zulassen möchte, dass eine Klasse instanziiert wird und nichts von dieser Klasse erbt.
Mahoukov
Ja, das habe ich verstanden. Aber Ihre Lösung hilft beim Erstellen einer Singleton-Klasse, in der niemand init aufrufen soll. Nach meinem Wissen wird es im Fall einer abstrakten Klasse eine Klasse erben. sonst was ist die Verwendung der abstrakten Klasse.
Sahil Doshi
0

Wenn Sie ein wenig ändern, was @redfood durch Anwenden des Kommentars von @ dotToString vorgeschlagen hat, haben Sie tatsächlich die Lösung, die von IGListKit von Instagram übernommen wurde .

  1. Erstellen Sie ein Protokoll für alle Methoden, deren Definition in der Basisklasse (abstrakt) keinen Sinn ergibt, dh sie benötigen bestimmte Implementierungen in den untergeordneten Klassen.
  2. Erstellen Sie eine Basisklasse (abstrakt), die dieses Protokoll nicht implementiert. Sie können dieser Klasse alle anderen Methoden hinzufügen, die für eine gemeinsame Implementierung sinnvoll sind.
  3. Wenn in Ihrem Projekt ein untergeordnetes Element von überall in eine AbstractClassMethode eingegeben oder von dieser ausgegeben werden muss, geben Sie es AbstractClass<Protocol>stattdessen wie folgt ein.

Da AbstractClassnicht implementiert Protocol, ist die einzige Möglichkeit, eine AbstractClass<Protocol>Instanz zu haben, die Unterklasse. Da AbstractClassallein nirgendwo im Projekt verwendet werden kann, wird es abstrakt.

Dies hindert natürlich nicht empfohlene Entwickler nicht daran, neue Methoden hinzuzufügen, auf die einfach Bezug genommen wird AbstractClass, was letztendlich dazu führen würde, dass eine Instanz der (nicht mehr) abstrakten Klasse zugelassen wird.

Beispiel aus der Praxis : IGListKit verfügt über eine Basisklasse, IGListSectionControllerdie das Protokoll nicht implementiert. IGListSectionTypeJede Methode, die eine Instanz dieser Klasse benötigt, fragt jedoch tatsächlich nach dem Typ IGListSectionController<IGListSectionType>. Daher gibt es keine Möglichkeit, ein Objekt vom Typ IGListSectionControllerfür irgendetwas zu verwenden, das in ihrem Framework nützlich ist.

Gobe
quelle
0

Tatsächlich verfügt Objective-C nicht über abstrakte Klassen, aber Sie können Protokolle verwenden , um den gleichen Effekt zu erzielen. Hier ist das Beispiel:

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
YorkFish
quelle
0

Ein einfaches Beispiel für die Erstellung einer abstrakten Klasse

// 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
Say2Manuj
quelle
-3

Können Sie nicht einfach einen Delegaten erstellen?

Ein Delegat ist wie eine abstrakte Basisklasse in dem Sinne, dass Sie sagen, welche Funktionen definiert werden müssen, aber Sie definieren sie nicht wirklich.

Wenn Sie dann Ihren Delegaten (dh die abstrakte Klasse) implementieren, werden Sie vom Compiler gewarnt, für welche optionalen und obligatorischen Funktionen Sie das Verhalten definieren müssen.

Das klingt für mich nach einer abstrakten Basisklasse.

user1709076
quelle