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?
objective-c
abstract-class
Jonathan Arbogast
quelle
quelle
Antworten:
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:
Wenn Ihre Methode einen Wert zurückgibt, ist die Verwendung etwas einfacher
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.
quelle
@protocol
, aber Sie können die Methoden dort nicht definieren.+ (instancetype)exceptionForCallingAbstractMethod:(SEL)selector
funktioniert dies sehr gut.doesNotRecognizeSelector
.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:
Sie können dies auch für init tun.
quelle
NSObject
Referenz 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 :)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:
dann können Sie dies in Ihren Methoden verwenden
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
(anstattNSInternalInconsistencyException
), da das LaufzeitsystemdoesNotRecognizeSelector
dies als Reaktion auf den Aufruf auslöst (sieheNSObject
Dokumente).quelle
universe.thing
aber er erweitert sich auf[Universe universe].thing
. Großer Spaß, Tausende von Buchstaben des Codes speichern ...#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
.__PRETTY_FUNCTION__
überall über ein DLog-Makro (...), wie hier vorgeschlagen: stackoverflow.com/a/969291/38557 .#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
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 zuHomeViewController - method not implemented
erfahren , wo HomeViewController von Base geerbt wurde - dies wird mehr Informationen gebenDie Lösung, die ich gefunden habe, ist:
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.
quelle
NSObject
). Wenn Sie dann das Protokoll und die Basisklassendeklaration in dieselbe Headerdatei einfügen, ist sie von einer abstrakten Klasse kaum zu unterscheiden.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...
Ich mache auch Folgendes, um die Initialisierung der abstrakten Klasse über den Standardinitialisierer zu verhindern.
quelle
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:
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.
quelle
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
@dynamic
Eigenschaften, 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 :
[self doesNotRecognizeSelector:_cmd];
...__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.-[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:
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
__builtin_unreachable()
mag die Leute überraschen, aber es ist auch leicht zu verstehen.)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?
quelle
__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.Verwenden
@property
und@dynamic
könnte auch funktionieren. Wenn Sie eine dynamische Eigenschaft deklarieren und keine übereinstimmende Methodenimplementierung angeben, wird weiterhin alles ohne Warnungen kompiliert, undunrecognized selector
zur 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.quelle
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-
@interface
Tag die "abstrakten" Methoden:Wenn ich noch einen Schritt weiter gehe, erstelle ich ein Makro:
So können Sie Folgendes tun:
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.
quelle
-init
Ihre Unterklasse eine Methode enthält.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.
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?
quelle
Eine andere Alternative
Überprüfen Sie einfach die Klasse in der Abstract-Klasse und Assert or Exception, was auch immer Sie möchten.
Dadurch entfällt die Notwendigkeit des Überschreibens
init
quelle
(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):
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.
quelle
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:
quelle
Wahrscheinlich sollten solche Situationen nur zur Entwicklungszeit auftreten, daher könnte dies funktionieren:
quelle
Sie können eine von @Yar vorgeschlagene Methode verwenden (mit einigen Änderungen):
Hier erhalten Sie eine Nachricht wie:
Oder Behauptung:
In diesem Fall erhalten Sie:
Sie können auch Protokolle und andere Lösungen verwenden - dies ist jedoch eine der einfachsten.
quelle
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.
quelle
Normalerweise deaktiviere ich nur die init-Methode in einer Klasse, die ich abstrahieren möchte:
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.
quelle
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 .
AbstractClass
Methode eingegeben oder von dieser ausgegeben werden muss, geben Sie esAbstractClass<Protocol>
stattdessen wie folgt ein.Da
AbstractClass
nicht implementiertProtocol
, ist die einzige Möglichkeit, eineAbstractClass<Protocol>
Instanz zu haben, die Unterklasse. DaAbstractClass
allein 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,
IGListSectionController
die das Protokoll nicht implementiert.IGListSectionType
Jede Methode, die eine Instanz dieser Klasse benötigt, fragt jedoch tatsächlich nach dem TypIGListSectionController<IGListSectionType>
. Daher gibt es keine Möglichkeit, ein Objekt vom TypIGListSectionController
für irgendetwas zu verwenden, das in ihrem Framework nützlich ist.quelle
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
TestProtocol.h
TestProtocol.m
quelle
Ein einfaches Beispiel für die Erstellung einer abstrakten Klasse
quelle
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.
quelle