Ich neige dazu, nur die Notwendigkeiten (gespeicherte Eigenschaften, Initialisierer) in meine Klassendefinitionen aufzunehmen und alles andere in ihre eigenen zu verschieben extension
, ähnlich einem extension
logischen Block, mit dem ich auch gruppieren würde // MARK:
.
Für eine UIView-Unterklasse würde ich beispielsweise eine Erweiterung für Layout-bezogene Dinge erhalten, eine für das Abonnieren und Behandeln von Ereignissen und so weiter. In diesen Erweiterungen muss ich zwangsläufig einige UIKit-Methoden überschreiben, z layoutSubviews
. Ich habe bis heute keine Probleme mit diesem Ansatz bemerkt.
Nehmen Sie diese Klassenhierarchie zum Beispiel:
public class C: NSObject {
public func method() { print("C") }
}
public class B: C {
}
extension B {
override public func method() { print("B") }
}
public class A: B {
}
extension A {
override public func method() { print("A") }
}
(A() as A).method()
(A() as B).method()
(A() as C).method()
Die Ausgabe ist A B C
. Das macht für mich wenig Sinn. Ich habe gelesen, dass Protokollerweiterungen statisch versendet werden, aber dies ist kein Protokoll. Dies ist eine reguläre Klasse, und ich erwarte, dass Methodenaufrufe zur Laufzeit dynamisch ausgelöst werden. Klar sollte der Anruf C
zumindest dynamisch versendet und produziert werden C
?
Wenn ich die Vererbung entferne NSObject
und C
eine Root-Klasse erstelle, beschwert sich der Compiler und sagt declarations in extensions cannot override yet
, worüber ich bereits gelesen habe. Aber wie NSObject
verändert es die Dinge , eine Wurzelklasse zu haben?
Beide Überschreibungen in ihre Klassendeklaration Verschieben erzeugt A A A
wie erwartet, bewegt nur B
‚produziert s A B B
, bewegt nur A
‘ s produziert C B C
von denen die letzte, macht absolut keinen Sinn für mich: nicht einmal derjenige statisch typisierte A
die erzeugt A
-Ausgang mehr!
Das Hinzufügen des dynamic
Schlüsselworts zur Definition oder eine Überschreibung scheint mir das gewünschte Verhalten "von diesem Punkt in der Klassenhierarchie abwärts" zu geben ...
Lassen Sie uns unser Beispiel in etwas weniger Konstruiertes ändern, was mich tatsächlich dazu gebracht hat, diese Frage zu stellen:
public class B: UIView {
}
extension B {
override public func layoutSubviews() { print("B") }
}
public class A: B {
}
extension A {
override public func layoutSubviews() { print("A") }
}
(A() as A).layoutSubviews()
(A() as B).layoutSubviews()
(A() as UIView).layoutSubviews()
Wir bekommen jetzt A B A
. Hier kann ich die layoutSubviews von UIView keinesfalls dynamisieren.
Wenn Sie beide Überschreibungen in ihre Klassendeklaration verschieben, erhalten Sie A A A
erneut, nur A oder nur B erhalten uns noch A B A
. dynamic
löst wieder meine Probleme.
Theoretisch könnte ich dynamic
alles hinzufügen , was override
ich jemals getan habe, aber ich habe das Gefühl, dass ich hier etwas anderes falsch mache.
Ist es wirklich falsch, extension
s zum Gruppieren von Code zu verwenden, wie ich es tue?
quelle
Antworten:
Erweiterungen können / sollten nicht überschrieben werden.
Es ist nicht möglich, Funktionen (wie Eigenschaften oder Methoden) in Erweiterungen zu überschreiben, wie im Apple Swift Guide dokumentiert.
Swift Developer Guide
Mit dem Compiler können Sie die Erweiterung überschreiben, um die Kompatibilität mit Objective-C zu gewährleisten. Aber es verstößt tatsächlich gegen die Sprachrichtlinie.
"Das erinnerte mich nur an Isaac Asimovs" Drei Gesetze der Robotik "
Erweiterungen ( syntaktischer Zucker ) definieren unabhängige Methoden, die ihre eigenen Argumente erhalten. Die Funktion, die aufgerufen wird,
layoutSubviews
hängt vom Kontext ab, den der Compiler kennt, wenn der Code kompiliert wird. UIView erbt von UIResponder, der von NSObject erbt, sodass das Überschreiben in der Erweiterung zulässig ist, dies jedoch nicht sein sollte .Es ist also nichts Falsches an der Gruppierung, aber Sie sollten in der Klasse überschreiben, nicht in der Erweiterung.
Hinweise zur Richtlinie
Sie können
override
eine Superklassenmethode nurload()
initialize()
in einer Erweiterung einer Unterklasse verwenden, wenn die Methode Objective-C-kompatibel ist.Daher können wir einen Blick darauf werfen, warum Sie damit kompilieren können
layoutSubviews
.Alle Swift-Apps werden innerhalb der Objective-C-Laufzeit ausgeführt, außer wenn reine Swift-Frameworks verwendet werden, die eine reine Swift-Laufzeit ermöglichen.
Wie wir herausgefunden haben, ruft die Objective-C-Laufzeit im Allgemeinen zwei Klassenhauptmethoden auf
load()
undinitialize()
automatisch, wenn Klassen in den Prozessen Ihrer App initialisiert werden.Bezüglich des
dynamic
ModifikatorsAus der Apple Developer Library (archive.org)
Mit dem
dynamic
Modifikator können Sie festlegen, dass der Zugriff auf Mitglieder dynamisch über die Objective-C-Laufzeit verteilt wird.Wenn Swift-APIs von der Objective-C-Laufzeit importiert werden, gibt es keine Garantie für den dynamischen Versand von Eigenschaften, Methoden, Indizes oder Initialisierern. Der Swift-Compiler kann den Mitgliederzugriff weiterhin devirtualisieren oder inline ausführen, um die Leistung Ihres Codes unter Umgehung der Objective-C-Laufzeit zu optimieren. 😳
So
dynamic
können Sie Ihre angewendet werdenlayoutSubviews
->UIView Class
da sie von Objective-C dargestellt ist und Zugriff auf dieses Element immer dann verwendet wird , um die Objective-C - Laufzeit.Deshalb ist der Compiler in dem Sie verwenden
override
unddynamic
.quelle
Eines der Ziele von Swift ist das statische Dispatching bzw. die Reduzierung des dynamischen Dispatchings. Obj-C ist jedoch eine sehr dynamische Sprache. Die Situation, die Sie sehen, ergibt sich aus der Verbindung zwischen den beiden Sprachen und der Art und Weise, wie sie zusammenarbeiten. Es sollte nicht wirklich kompiliert werden.
Einer der Hauptpunkte bei Erweiterungen ist, dass sie zum Erweitern und nicht zum Ersetzen / Überschreiben dienen. Sowohl aus dem Namen als auch aus der Dokumentation geht hervor, dass dies die Absicht ist. Wenn Sie den Link zu Obj-C aus Ihrem Code entfernen (
NSObject
als Oberklasse entfernen ), wird er nicht kompiliert.Der Compiler versucht also zu entscheiden, was er statisch versenden kann und was er dynamisch versenden muss, und er fällt aufgrund der Obj-C-Verknüpfung in Ihrem Code durch eine Lücke. Der Grund
dynamic
, warum 'funktioniert', ist, dass es die Obj-C-Verknüpfung für alles erzwingt, sodass alles immer dynamisch ist.Es ist also nicht falsch, Erweiterungen für die Gruppierung zu verwenden, das ist großartig, aber es ist falsch, Erweiterungen zu überschreiben. Alle Überschreibungen sollten sich in der Hauptklasse selbst befinden und an Erweiterungspunkte gerichtet werden.
quelle
supportedInterfaceOrientations
inUINavigationController
(für die Zwecke , die unterschiedlichen Ansichten in verschiedenen Orientierungen), sollten Sie eine benutzerdefinierte Klasse keine Erweiterung verwenden? Viele Antworten schlagen vor, eine Erweiterung zum Überschreiben zu verwendensupportedInterfaceOrientations
, würden aber gerne Klarheit schaffen. Vielen Dank!Es gibt eine Möglichkeit, eine saubere Trennung von Klassensignatur und Implementierung (in Erweiterungen) zu erreichen und gleichzeitig die Möglichkeit von Überschreibungen in Unterklassen beizubehalten. Der Trick besteht darin, Variablen anstelle der Funktionen zu verwenden
Wenn Sie sicherstellen, dass jede Unterklasse in einer separaten schnellen Quelldatei definiert ist, können Sie berechnete Variablen für die Überschreibungen verwenden, während die entsprechende Implementierung in Erweiterungen sauber organisiert bleibt. Dadurch werden die "Regeln" von Swift umgangen und die API / Signatur Ihrer Klasse an einem Ort übersichtlich organisiert:
...
...
Die Erweiterung jeder Klasse kann dieselben Methodennamen für die Implementierung verwenden, da sie privat und für einander nicht sichtbar sind (solange sie sich in separaten Dateien befinden).
Wie Sie sehen können, funktioniert die Vererbung (unter Verwendung des Variablennamens) ordnungsgemäß mit super.variablename
quelle
Diese Antwort richtete sich nicht an das OP, abgesehen von der Tatsache, dass ich mich inspiriert fühlte, durch seine Aussage zu antworten: "Ich neige dazu, nur die Notwendigkeiten (gespeicherte Eigenschaften, Initialisierer) in meine Klassendefinitionen aufzunehmen und alles andere in ihre eigene Erweiterung zu verschieben. .. ". Ich bin hauptsächlich ein C # -Programmierer, und in C # kann man zu diesem Zweck Teilklassen verwenden. Beispielsweise platziert Visual Studio das UI-bezogene Material mithilfe einer Teilklasse in einer separaten Quelldatei und lässt Ihre Hauptquelldatei übersichtlich, damit Sie diese Ablenkung nicht haben.
Wenn Sie nach "schnelle Teilklasse" suchen, finden Sie verschiedene Links, bei denen Swift-Anhänger sagen, dass Swift keine Teilklassen benötigt, da Sie Erweiterungen verwenden können. Interessanterweise lautet der erste Suchvorschlag, wenn Sie "schnelle Erweiterung" in das Google-Suchfeld eingeben, "schnelle Erweiterung überschreiben", und im Moment ist diese Frage zum Stapelüberlauf der erste Treffer. Ich verstehe das so, dass Probleme mit (fehlenden) Überschreibungsfunktionen das am häufigsten gesuchte Thema im Zusammenhang mit Swift-Erweiterungen sind, und unterstreicht die Tatsache, dass Swift-Erweiterungen möglicherweise keine Teilklassen ersetzen können, zumindest wenn Sie abgeleitete Klassen in Ihrem verwenden Programmierung.
Um eine langwierige Einführung zu verkürzen, stieß ich auf dieses Problem in einer Situation, in der ich einige Boilerplate- / Gepäckmethoden aus den Hauptquelldateien für Swift-Klassen verschieben wollte, die mein C # -to-Swift-Programm generierte. Nachdem ich auf das Problem gestoßen war, dass diese Methoden nach dem Verschieben in Erweiterungen nicht überschrieben werden dürfen, implementierte ich die folgende einfältige Problemumgehung. Die Haupt-Swift-Quelldateien enthalten noch einige winzige Stub-Methoden, die die realen Methoden in den Erweiterungsdateien aufrufen, und diese Erweiterungsmethoden erhalten eindeutige Namen, um das Überschreibungsproblem zu vermeiden.
.
.
.
.
Wie ich in meiner Einführung sagte, beantwortet dies die Frage des OP nicht wirklich, aber ich hoffe, dass diese einfältige Problemumgehung für andere hilfreich sein kann, die Methoden von den Hauptquelldateien in Erweiterungsdateien verschieben und auf die Nr. 1 stoßen möchten - Problem überschreiben.
quelle
Verwenden Sie POP (Protocol-Oriented Programming), um Funktionen in Erweiterungen zu überschreiben.
quelle