web-dev-qa-db-de.com

Überschreibende Methoden in Swift Erweiterungen

Ich neige dazu, nur die Notwendigkeiten (gespeicherte Eigenschaften, Initialisierer) in meine Klassendefinitionen aufzunehmen und alles andere in einen eigenen extension zu verschieben, ähnlich einem extension pro logischem Block, den ich mit // MARK: gruppieren würde. auch.

Für eine UIView-Unterklasse würde ich beispielsweise eine Erweiterung für layoutbezogene Inhalte, eine Erweiterung für das Abonnieren und Behandeln von Ereignissen usw. haben. Bei diesen Erweiterungen muss ich zwangsläufig einige UIKit-Methoden außer Kraft setzen, 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 ergibt 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. Es ist klar, dass der Aufruf von C mindestens dynamisch gesendet werden und C erzeugen sollte.

Wenn ich die Vererbung von NSObject entferne und C zu einer Root-Klasse mache, beklagt sich der Compiler mit declarations in extensions cannot override yet, worüber ich bereits gelesen habe. Aber wie ändert es sich, NSObject als Root-Klasse zu haben?

Das Verschieben beider Überschreibungen in ihre Klassendeklaration führt zu A A A wie erwartet, nur das Verschieben von B führt zu A B B, nur das Verschieben von A führt zu C B C, von denen der letzte für mich absolut sinnlos ist: nicht Sogar die statisch auf A getippte erzeugt die A - Ausgabe nicht mehr!

Das Hinzufügen des Schlüsselworts dynamic zur Definition oder eines Overrides scheint mir das gewünschte Verhalten zu geben 'von diesem Punkt in der Klassenhierarchie abwärts' ...

Lassen Sie uns unser Beispiel in etwas weniger Konstruiertes ändern, was mich 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 UIViews layoutSubviews keinesfalls dynamisch machen.

Wenn Sie beide Überschreibungen in ihre Klassendeklaration verschieben, erhalten wir erneut einen A A A. Nur A-Werte oder nur B-Werte erhalten uns noch einen A B A. dynamic löst wieder meine Probleme.

Theoretisch könnte ich dynamic zu allen override hinzufügen, die ich jemals gemacht habe, aber ich habe das Gefühl, dass ich hier etwas anderes falsch mache.

Ist es wirklich falsch, extension s zum Gruppieren von Code wie ich zu verwenden?

114

Erweiterungen können/sollten nicht überschrieben werden.

Es ist nicht möglich, Funktionen (wie Eigenschaften oder Methoden) in Erweiterungen zu überschreiben, wie im Swift - Handbuch von Apple dokumentiert.

Erweiterungen können einem Typ neue Funktionen hinzufügen, vorhandene Funktionen jedoch nicht überschreiben.

Apple Developer Guide

Mit dem Compiler können Sie die Erweiterung aus Gründen der Kompatibilität mit Objective-C überschreiben. Aber es verstößt tatsächlich gegen die Sprachrichtlinie.

Das hat mich nur an Isaac Asimovs " Drei Gesetze der Robotik " erinnert.

Erweiterungen ( syntaktischer Zucker) definieren unabhängige Methoden, die ihre eigenen Argumente erhalten. Die Funktion, die aufgerufen wird, d. H. layoutSubviews, hängt vom Kontext ab, den der Compiler beim Kompilieren des Codes kennt. UIView erbt von UIResponder, das von NSObject erbt so ist das Überschreiben in der Erweiterung zulässig, sollte es aber nicht sein.

An der Gruppierung ist also nichts auszusetzen, aber Sie sollten die Klasse überschreiben, nicht die Erweiterung.

Hinweise zur Richtlinie

Sie können eine Methode der Oberklasse nur override, d. H. load()initialize() in einer Erweiterung einer Unterklasse, wenn die Methode Objective-C-kompatibel ist.

Daher können wir uns ansehen, warum es Ihnen erlaubt, mit layoutSubviews zu kompilieren.

Alle Swift Apps werden innerhalb der Objective-C-Laufzeit ausgeführt, mit Ausnahme von reinen Swift-only-Frameworks, die eine reine Swift-Laufzeit ermöglichen.

Wie wir herausgefunden haben, ruft die Objective-C-Laufzeit im Allgemeinen zwei Klassenhauptmethoden load() und initialize() automatisch auf, wenn Klassen in den Prozessen Ihrer App initialisiert werden.

In Bezug auf den Modifikator dynamic

Aus der iOS Developer Library

Mit dem Modifikator dynamic 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 Garantien für den dynamischen Versand von Eigenschaften, Methoden, Indizes oder Initialisierern. Der Swift - Compiler kann den Memberzugriff weiterhin dedirtualisieren oder inline ausführen, um die Leistung Ihres Codes unter Umgehung der Objective-C-Laufzeit zu optimieren. ??? ?

Daher kann dynamic auf Ihr layoutSubviews -> UIView Class Angewendet werden, da es durch Objective-C dargestellt wird und der Zugriff auf dieses Mitglied immer über die Objective-C-Laufzeit verwendet wird.

Deshalb erlaubt der Compiler die Verwendung von override und dynamic.

185
tymac

Eines der Ziele von Swift ist statisches Dispatching oder vielmehr die Reduzierung des dynamischen Dispatchings. Obj-C ist jedoch eine sehr dynamische Sprache. Die Situation, die Sie sehen, entspringt dem Zusammenhang zwischen Die 2 Sprachen und die 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 gedacht sind. Sowohl aus dem Namen als auch aus der Dokumentation geht hervor, dass dies beabsichtigt ist. Wenn Sie den Link zu Obj-C aus Ihrem Code entfernen (entfernen Sie NSObject als Superklasse), wird er nicht kompiliert.

Der Compiler versucht also zu entscheiden, was er statisch versenden kann und was er dynamisch versenden muss. Aufgrund der Obj-C-Verknüpfung in Ihrem Code tritt eine Lücke auf. Der Grund, warum dynamic 'funktioniert', ist, dass es die Verknüpfung von Obj-C auf alles erzwingt, sodass alles immer dynamisch ist.

Es ist also nicht falsch, Erweiterungen zum Gruppieren zu verwenden, das ist großartig, aber es ist falsch, Erweiterungen zu überschreiben. Alle Überschreibungen sollten in der Hauptklasse selbst sein und auf Erweiterungspunkte hinweisen.

16
Wain

Es gibt eine Möglichkeit, die Klassensignatur und -implementierung (in Erweiterungen) sauber voneinander zu trennen, während Überschreibungen in Unterklassen weiterhin möglich sind. Der Trick besteht darin, Variablen anstelle der Funktionen zu verwenden

Wenn Sie sicherstellen, dass Sie jede Unterklasse in einer separaten Swift - Quelldatei definieren, können Sie berechnete Variablen für die Außerkraftsetzungen verwenden und gleichzeitig die entsprechende Implementierung in Erweiterungen übersichtlich organisieren. Dadurch werden die "Regeln" von Swift umgangen und die API/Signatur Ihrer Klasse übersichtlich an einem Ort organisiert:

 // ---------- BaseClass.Swift -------------

 public class BaseClass
 {
     public var method1:(Int) -> String { return doMethod1 }

     public init() {}
 }

 // the extension could also be in a separate file  
 extension BaseClass
 {    
     private func doMethod1(param:Int) -> String { return "BaseClass \(param)" }
 }

...

 // ---------- ClassA.Swift ----------

 public class A:BaseClass
 {
    override public var method1:(Int) -> String { return doMethod1 }
 }

 // this extension can be in a separate file but not in the same
 // file as the BaseClass extension that defines its doMethod1 implementation
 extension A
 {
    private func doMethod1(param:Int) -> String 
    { 
       return "A \(param) added to \(super.method1(param))" 
    }
 }

...

 // ---------- ClassB.Swift ----------
 public class B:A
 {
    override public var method1:(Int) -> String { return doMethod1 }
 }

 extension B
 {
    private func doMethod1(param:Int) -> String 
    { 
       return "B \(param) added to \(super.method1(param))" 
    }
 }

Die Erweiterungen jeder Klasse können für die Implementierung dieselben Methodennamen verwenden, da sie privat und für einander nicht sichtbar sind (sofern sie sich in separaten Dateien befinden).

Wie Sie sehen können, funktioniert die Vererbung (unter Verwendung des Variablennamens) ordnungsgemäß unter Verwendung von super.variablename

 BaseClass().method1(123)         --> "BaseClass 123"
 A().method1(123)                 --> "A 123 added to BaseClass 123"
 B().method1(123)                 --> "B 123 added to A 123 added to BaseClass 123"
 (B() as A).method1(123)          --> "B 123 added to A 123 added to BaseClass 123"
 (B() as BaseClass).method1(123)  --> "B 123 added to A 123 added to BaseClass 123"
8
Alain T.

Verwenden Sie POP (Protocol Oriented Programming), um Funktionen in Erweiterungen zu überschreiben.

protocol AProtocol {
    func aFunction()
}

extension AProtocol {
    func aFunction() {
        print("empty")
    }
}

class AClass: AProtocol {

}

extension AClass {
    func aFunction() {
        print("not empty")
    }
}

let cls = AClass()
cls.aFunction()
1
Winter

Diese Antwort bezog sich nicht auf das OP, abgesehen von der Tatsache, dass ich mich durch seine Aussage inspiriert fühlte: "Ich tendiere dazu, nur die Notwendigkeiten (gespeicherte Eigenschaften, Initialisierer) in meine Klassendefinitionen aufzunehmen und alles andere in ihre eigene Erweiterung zu verschieben. .. ". Ich bin in erster Linie ein C # -Programmierer, und in C # kann man zu diesem Zweck Teilklassen verwenden. Beispielsweise platziert Visual Studio die UI-bezogenen Inhalte mithilfe einer Teilklasse in einer separaten Quelldatei und lässt Ihre Hauptquelldatei übersichtlich, damit Sie nicht von dieser Ablenkung abgelenkt werden.

Wenn Sie nach "Swift-Teilklasse" suchen, finden Sie verschiedene Links, auf denen Swift Anhänger sagen, dass Swift keine Teilklassen benötigt, da Sie Erweiterungen verwenden können Wenn Sie "Swift extension" in das Google - Suchfeld eingeben, lautet der erste Suchvorschlag "Swift extension override", und im Moment ist diese Stapelüberlauf - Frage der erste Treffer of) override-Funktionen sind das am häufigsten gesuchte Thema im Zusammenhang mit Swift extensions und hebt die Tatsache hervor, dass Swift extensions partielle Klassen möglicherweise nicht ersetzen kann, at Zumindest, wenn Sie abgeleitete Klassen in Ihrer Programmierung verwenden.

Kurz gesagt, ich bin auf dieses Problem gestoßen, als ich einige Boilerplate-/Baggage-Methoden aus den Hauptquelldateien für Swift Klassen verschieben wollte, die mein C # -to-Swift-Programm wurde generiert. Nachdem ich auf das Problem gestoßen war, dass diese Methoden nicht überschrieben werden dürfen, nachdem sie auf Erweiterungen verschoben wurden, implementierte ich die folgende einfache Problemumgehung: Die Hauptquelle Swift Dateien enthalten immer noch einige winzige Stubmethoden, die die realen Methoden in den Erweiterungsdateien aufrufen, und diese Erweiterungsmethoden erhalten eindeutige Namen, um das Überschreibungsproblem zu vermeiden.

public protocol PCopierSerializable {

   static func getFieldTable(mCopier : MCopier) -> FieldTable
   static func createObject(initTable : [Int : Any?]) -> Any
   func doSerialization(mCopier : MCopier)
}

.

public class SimpleClass : PCopierSerializable {

   public var aMember : Int32

   public init(
               aMember : Int32
              ) {
      self.aMember = aMember
   }

   public class func getFieldTable(mCopier : MCopier) -> FieldTable {
      return getFieldTable_SimpleClass(mCopier: mCopier)
   }

   public class func createObject(initTable : [Int : Any?]) -> Any {
      return createObject_SimpleClass(initTable: initTable)
   }

   public func doSerialization(mCopier : MCopier) {
      doSerialization_SimpleClass(mCopier: mCopier)
   }
}

.

extension SimpleClass {

   class func getFieldTable_SimpleClass(mCopier : MCopier) -> FieldTable {
      var fieldTable : FieldTable = [ : ]
      fieldTable[376442881] = { () in try mCopier.getInt32A() }  // aMember
      return fieldTable
   }

   class func createObject_SimpleClass(initTable : [Int : Any?]) -> Any {
      return SimpleClass(
                aMember: initTable[376442881] as! Int32
               )
   }

   func doSerialization_SimpleClass(mCopier : MCopier) {
      mCopier.writeBinaryObjectHeader(367620, 1)
      mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
   }
}

.

public class DerivedClass : SimpleClass {

   public var aNewMember : Int32

   public init(
               aNewMember : Int32,
               aMember : Int32
              ) {
      self.aNewMember = aNewMember
      super.init(
                 aMember: aMember
                )
   }

   public class override func getFieldTable(mCopier : MCopier) -> FieldTable {
      return getFieldTable_DerivedClass(mCopier: mCopier)
   }

   public class override func createObject(initTable : [Int : Any?]) -> Any {
      return createObject_DerivedClass(initTable: initTable)
   }

   public override func doSerialization(mCopier : MCopier) {
      doSerialization_DerivedClass(mCopier: mCopier)
   }
}

.

extension DerivedClass {

   class func getFieldTable_DerivedClass(mCopier : MCopier) -> FieldTable {
      var fieldTable : FieldTable = [ : ]
      fieldTable[376443905] = { () in try mCopier.getInt32A() }  // aNewMember
      fieldTable[376442881] = { () in try mCopier.getInt32A() }  // aMember
      return fieldTable
   }

   class func createObject_DerivedClass(initTable : [Int : Any?]) -> Any {
      return DerivedClass(
                aNewMember: initTable[376443905] as! Int32,
                aMember: initTable[376442881] as! Int32
               )
   }

   func doSerialization_DerivedClass(mCopier : MCopier) {
      mCopier.writeBinaryObjectHeader(367621, 2)
      mCopier.serializeProperty(376443905, .eInt32, { () in mCopier.putInt32(aNewMember) } )
      mCopier.serializeProperty(376442881, .eInt32, { () in mCopier.putInt32(aMember) } )
   }
}

Wie ich in meiner Einführung sagte, ist die Frage des OP damit nicht wirklich beantwortet, aber ich hoffe, dass diese einfältige Problemumgehung für andere hilfreich sein kann, die Methoden von den Hauptquelldateien in Erweiterungsdateien verschieben und in die Nr -Überschreibungsproblem.

1
RenniePet