web-dev-qa-db-de.com

Überschreiben der Swift Protocol Extensions

Ich experimentiere mit den Swift-Protokollerweiterungen, und ich fand das ziemlich verwirrend. Könnten Sie mir helfen, wie ich das gewünschte Ergebnis erzielen kann?

Siehe die Kommentare zu den letzten 4 Zeilen des Codes. (Sie können es kopieren und in den XCode7-Spielplatz einfügen, wenn Sie möchten). Vielen Dank!!

//: Playground - noun: a place where people can play

import UIKit

protocol Color { }
extension Color {  var color : String { return "Default color" } }

protocol RedColor: Color { }
extension RedColor { var color : String { return "Red color" } }


protocol PrintColor {

     func getColor() -> String
}

extension PrintColor where Self: Color {

    func getColor() -> String {

        return color
    }
}


class A: Color, PrintColor { }
class B: A, RedColor { }


let colorA = A().color // is "Default color" - OK
let colorB = B().color // is "Red color" - OK


let a = A().getColor() // is "Default color" - OK
let b = B().getColor() // is "Default color" BUT I want it to be "Red color"
43
VojtaStavik

Die kurze Antwort ist, dass Protokollerweiterungen keinen Klassenpolymorphismus ausführen. Dies macht einen gewissen Sinn, weil ein Protokoll von einer Struktur oder einer Enumeration übernommen werden kann und weil wir nicht die bloße Annahme eines Protokolls zur Einführung eines dynamischen Dispatchs wollen, wenn dies nicht notwendig ist.

In getColor() bedeutet die Instanzvariable color (die möglicherweise genauer als self.color geschrieben wird) nicht das, was Sie ihrer Meinung nach tun, weil Sie klassenpolymorph denken und das Protokoll nicht. Das funktioniert also:

let colorB = B().color // is "Red color" - OK

... weil Sie eine Klasse fordern, um color aufzulösen, aber dies tut nicht das, was Sie erwarten:

let b = B().getColor() // is "Default color" BUT I want it to be "Red color"

... weil die getColor-Methode vollständig in einer Protokollerweiterung definiert ist. Sie können das Problem beheben, indem Sie getColor in B neu definieren:

class B: A, RedColor {
    func getColor() -> String {
        return self.color
    }
}

Jetzt wird getColor der Klasse aufgerufen, und sie hat eine polymorphe Vorstellung davon, was self ist.

42
matt

Ich habe es geschafft, indem ich color für Color definiert und die Implementierungsliste für B gewechselt habe. Nicht sehr gut, wenn B ein A sein muss.

protocol Color {
    var color : String { get }
}

protocol RedColor: Color {

}

extension Color {
    var color : String {
        get {return "Default color"}
    }
}

extension RedColor {
    var color : String {
        get {return "Red color"}
    }
}

protocol PrintColor {
    func getColor() -> String
}

extension PrintColor where Self: Color {
    func getColor() -> String {
        return color
    }
}

class A : Color, PrintColor {

}

class B : RedColor, PrintColor {

}

let a = A().getColor() // "Default color"
let b = B().getColor() // "Red color"
4
Ian Warburton

Hier gibt es zwei sehr unterschiedliche Probleme: Das dynamische Verhalten von Protokollen und die Auflösung von Protokoll-Standard-Implementierungen.

  1. In dynamischer Hinsicht können wir das Problem anhand eines einfachen Beispiels veranschaulichen:

    protocol Color { }
    
    extension Color {
        var color: String { return "Default color" }
    }
    
    class BlueBerry: Color {
        var color: String { return "Blue color" }
    }
    
    let berry = BlueBerry()
    print("\(berry.color)")                 // prints "Blue color", as expected
    
    let colorfulThing: Color = BlueBerry()
    print("\(colorfulThing.color)")         // prints "Default color"!
    

    Wenn Sie in your answer darauf hinweisen, können Sie das dynamische Verhalten abrufen, wenn Sie color als Teil des ursprünglichen Color-Protokolls definieren (dh der Compiler so angewiesen wird, vernünftigerweise zu erwarten, dass die konformen Klassen diese Methode implementieren und nur das Protokoll verwenden Implementierung, wenn keine gefunden wird):

    protocol Color {
        var color: String { get }
    }
    
    ...
    
    let colorfulThing: Color = BlueBerry()
    print("\(colorfulThing.color)")         // now prints "Blue color", as expected
    
  2. Nun fragen Sie in Ihre Antwort , warum dies ein wenig auseinander fällt, wenn B eine Unterklasse von A ist. 

    Ich denke, es hilft zu bedenken, dass die Methodenimplementierungen in Protokollerweiterungen "Standard" -Implementierungen sind, d. H. Implementierungen, die verwendet werden sollen, wenn die konforme Klasse sie selbst nicht implementiert. Die Quelle der Verwirrung in Ihrem Fall rührt von der Tatsache her, dass BRedColor entspricht, die eine Standardimplementierung für color hat, aber B ist auch eine Unterklasse von A, die Color entspricht, die eine andere Standardimplementierung von color hat.

    Wir können uns also über Swifts Umgang mit dieser Situation in Zweifel ziehen (persönlich würde ich eher eine Warnung vor dieser inhärent mehrdeutigen Situation sehen), aber die Wurzel des Problems besteht meines Erachtens darin, dass es zwei unterschiedliche Hierarchien gibt (die OOP Objekthierarchie von Unterklassen und der POP-Protokollhierarchie der Protokollvererbung), und dies führt zu zwei konkurrierenden "Standard" -Implementierungen.

Ich weiß, dass dies eine alte Frage ist, also sind Sie wahrscheinlich längst zu anderen Dingen weitergegangen, was in Ordnung ist. Wenn Sie jedoch immer noch Probleme haben, wie Sie diesen Code richtig umgestalten können, teilen Sie uns ein wenig mit, was diese Klassenhierarchie und was diese Protokollvererbung tatsächlich darstellt, und wir können möglicherweise konkreter beraten werden. Dies ist einer der Fälle, in denen abstrakte Beispiele das Thema weiter verwirren. Mal sehen, was die Typen/Protokolle wirklich sind. (Wenn Sie Arbeitscode haben, kann http://codereview.stackexchange.com der bessere Ort sein.)

4
Rob

Ich bin auf dieses Problem gestoßen, als ich versuchte, eine "optionale" Methode über ein Protokoll zu implementieren. Kann in Strukturen, in Klassen, die nicht erben, und auch in Klassen, die von einer Basis erben, die eine Nicht-Protokoll-Standardmethode implementiert, die funktionieren kann, verwendet werden. Der einzige Fall, der nicht funktioniert, ist eine Klasse, die von einer Basis erbt, die Konformität deklariert, aber keine eigene "Nicht-Standard" -Implementierung bereitstellt. In diesem Fall ist die Standarderweiterung der Protokollerweiterung in die Basisklasse "eingebacken" und kann nicht überschrieben oder neu definiert werden. 

Einfaches Beispiel:

typealias MyFunction = () -> ()
protocol OptionalMethod {
    func optionalMethod() -> MyFunction?
    func executeOptionalMethod()
}
extension OptionalMethod {
    func optionalMethod() -> MyFunction? { return nil }
    func executeOptionalMethod() {
        if let myFunc = self.optionalMethod() {
            myFunc()
        } else {
            print("Type \(self) has not implemented `optionalMethod`")
        }
    }
}

class A: OptionalMethod {
}
class B: A {
    func optionalMethod() -> MyFunction? {
        return { print("Hello optional method") }
    }
}
struct C: OptionalMethod {
    func optionalMethod() -> MyFunction? {
        return { print("Hello optionalMethod") }
    }
}
class D: OptionalMethod {
    func optionalMethod() -> MyFunction? {
        return { print("Hello optionalMethod") }
    }
}
class E: D {
    override func optionalMethod() -> MyFunction? {
        return { print("Hello DIFFERENT optionalMethod") }
    }
}
/* Attempt to get B to declare its own conformance gives:
// error: redundant conformance of 'B2' to protocol 'OptionalMethod'
class B2: A, OptionalMethod {
    func optionalMethod() -> MyFunction? {
        return { print("Hello optional method") }
    }
}
*/
class A2: OptionalMethod {
    func optionalMethod() -> MyFunction? {
        return nil
    }
}
class B2: A2 {
    override func optionalMethod() -> MyFunction? {
        return { print("Hello optionalMethod") }
    }
}

let a = A() // Class A doesn't implement & therefore defaults to protocol extension implementation
a.executeOptionalMethod() // Type __lldb_expr_201.A has not implemented `optionalMethod`
let b = B() // Class B implements its own, but "inherits" implementation from superclass A
b.executeOptionalMethod() // Type __lldb_expr_205.B has not implemented `optionalMethod`
let c = C() // Struct C implements its own, and works
c.executeOptionalMethod() // Hello optionalMethod
let d = D() // Class D implements its own, inherits from nothing, and works
d.executeOptionalMethod() // Hello optionalMethod
let e = E() // Class E inherits from D, but overrides, and works
e.executeOptionalMethod() // Hello DIFFERENT optionalMethod
let a2 = A2() // Class A2 implements the method, but returns nil, (equivalent to A)
a2.executeOptionalMethod() // Type __lldb_expr_334.A2 has not implemented `optionalMethod`
let b2 = B2() // Class B2 overrides A2's "nil" implementation, and so works
b2.executeOptionalMethod() // Hello optionalMethod
0
Grimxn

Hinweis: Die vorgeschlagene Lösung "Definieren von color als Teil des ursprünglichen Color-Protokolls" löst das Problem nicht, wenn Vererbung vorliegt, z. RedBerry erbt von BlueBerry, das dem Protokoll Color entspricht. 

protocol Color {
    var color: String { get }
}

extension Color {
    var color: String { return "Default color" }
}

class BlueBerry: Color {
    //    var color: String { return "Blue color" }
}

class RedBerry: BlueBerry {
    var color: String { return "Red color" }
}

let berry = RedBerry()
print(berry.color)             // Red color

let colorfulThing: Color = RedBerry()
print(colorfulThing.color)     // Actual: Default color, Expected: Red color
0
Grand M