Überschreiben der Superclass-Eigenschaft mit unterschiedlichem Typ in Swift

128

Kann jemand in Swift erklären, wie eine Eigenschaft einer Oberklasse mit einem anderen Objekt überschrieben wird, das von der ursprünglichen Eigenschaft untergeordnet ist?

Nehmen Sie dieses einfache Beispiel:

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    override let chassis = RacingChassis() //Error here
}

Dies gibt den Fehler:

Cannot override with a stored property 'chassis'

Wenn ich stattdessen ein Chassis als 'var' habe, wird folgende Fehlermeldung angezeigt:

Cannot override mutable property 'chassis' of type 'Chassis' with covariant type 'RacingChassis'

Das einzige, was ich in der Anleitung unter "Überschreiben von Eigenschaften" finden konnte, ist, dass wir den Getter und den Setter überschreiben müssen, was möglicherweise zum Ändern des Werts der Eigenschaft (wenn es 'var' ist) funktioniert, aber was ist mit dem Ändern der Eigenschaftsklasse ?

James
quelle

Antworten:

115

Mit Swift können Sie den Klassentyp von Variablen oder Eigenschaften nicht ändern. Stattdessen können Sie in der Unterklasse eine zusätzliche Variable erstellen, die den neuen Klassentyp behandelt:

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    var chassis = Chassis()
}
class RaceCar: Car {
    var racingChassis = RacingChassis()
    override var chassis: Chassis {
        get {
            return racingChassis
        }
        set {
            if let newRacingChassis = newValue as? RacingChassis {
                racingChassis = newRacingChassis
            } else {
                println("incorrect chassis type for racecar")
            }
        }
    }
}

Es scheint, dass man eine Eigenschaft mit der let-Syntax nicht deklarieren und mit var in ihrer Unterklasse überschreiben kann oder umgekehrt, was möglicherweise daran liegt, dass die Implementierung der Oberklasse möglicherweise nicht erwartet, dass sich diese Eigenschaft nach der Initialisierung ändert. In diesem Fall muss die Eigenschaft auch in der Oberklasse mit 'var' deklariert werden, damit sie mit der Unterklasse übereinstimmt (wie im obigen Snippet gezeigt). Wenn man den Quellcode in der Superklasse nicht ändern kann, ist es wahrscheinlich am besten, das aktuelle RaceCar zu zerstören und jedes Mal ein neues RaceCar zu erstellen, wenn das Chassis mutiert werden muss.

Strich
quelle
1
Entschuldigung, habe gerade meinen Kommentar gelöscht und dann Ihre Antwort gesehen. Ich hatte ein Problem, weil meine Superklasse in meinem realen Fall eine Objective-C-Klasse mit einer strongEigenschaft ist und beim Versuch, sie zu überschreiben, ein Fehler aufgetreten ist. Es scheint jedoch, dass ich übersehen habe, dass dies zu einem "implizit ausgepackten optionalen" ( chassis!) in führt Schnell, also override var chassis : Chassis!behebt es.
James
4
Dies funktioniert nicht mehr mit Swift 1.1 unter Xcode 6.1. Erzeugt einen Fehler: "Unveränderliches 'let' property 'chassis' kann nicht mit dem Getter eines 'var' überschrieben werden". Irgendwelche Ideen für eine bessere Lösung?
Darrarski
1
Funktioniert auch nicht mehr in Swift 1.2 unter Xcode 6.3 Beta. Die veränderbare Eigenschaft 'A' vom Typ 'Typ1' kann nicht mit dem kovarianten Typ 'Typ2'
überschrieben werden
10
Das ist wirklich lahm und ein Versagen von Swift, imo. Da RacingChassis ist ein Chassis, sollte der Compiler kein Problem haben , so dass Sie die Klasse des Objektes in einer Unterklasse verfeinern. Viele Sprachen erlauben dies, und wenn es nicht unterstützt wird, führt dies zu hässlichen Problemumgehungen. Nichts für ungut. : /
devios1
1
Optionale Optionen bieten die Möglichkeit, darauf hinzuweisen, dass eine Variable möglicherweise nicht vorhanden ist, da sie von einer Funktion bereitgestellt wurde, die den erwarteten Wert nicht mehr zurückgibt. Solche Fälle können auftreten, wenn die Funktion überschrieben wurde. Schauen Sie sich meine Antwort für Details an.
10

Das scheint zu funktionieren

class Chassis {
    func description() -> String {
        return "Chassis"
    }
}
class RacingChassis : Chassis {
    override func description() -> String {
        return "Racing Chassis"
    }

    func racingChassisMethod() -> String {
        return "Wrooom"
    }
}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    override var chassis: RacingChassis {
    get {
        return self.chassis
    }
    set {
        self.chassis = newValue
    }
    }
}

var car = Car()
car.chassis.description()

var raceCar = RaceCar()
raceCar.chassis.description()
raceCar.chassis.racingChassisMethod()
David Arve
quelle
1
Dies funktioniert, da die Chassis-Eigenschaft wie letin der CarKlasse definiert ist, was eine Änderung unmöglich macht. Wir können nur die chassisEigenschaft der RaceCarKlasse ändern .
David Arve
4
Dies funktioniert nicht für Swift 2.0. Es gibt "Fehler: kann unveränderliche 'let' Eigenschaft 'Chassis' nicht mit dem Getter eines 'var'
überschreiben
Dies funktioniert auch in Swift 4.0 nicht. Layground-Ausführung fehlgeschlagen: Fehler: MyPlaygrounds.playground: 14: 15: Fehler: Unveränderliches 'let' -Eigenschafts-Chassis kann nicht mit dem Getter eines 'var'-Override-var-Chassis überschrieben werden: RacingChassis {^ MyPlaygrounds.playground: 11: 6: Hinweis : Versuch, die Eigenschaft hier zu überschreiben, lass chassis = Chassis () ^
karim
7

Versuche dies:

class Chassis{
     var chassis{
         return "chassis"
     } 
}

class RacingChassis:Chassis{
     var racing{
         return "racing"
     } 
}

class Car<Type:Chassis> {
     let chassis: Type
     init(chassis:Type){
        self.chassis = chassis
     }
}

class RaceCar: Car<RacingChassis> {
     var description{
         return self.chassis.racing
     } 
}

Dann:

let racingChassis = RacingChassis()
let raceCar = RaceCar(chassis:racingChassis)
print(raceCar.description) //output:racing

Details unter http://www.mylonly.com/14957025459875.html

adimtxg0422
quelle
Ah ordentlich und sauber
Inder Kumar Rathore
3

Der bereitgestellte Solution Dash funktioniert gut, außer dass die Superklasse mit dem Schlüsselwort let und nicht mit var deklariert werden muss. Hier ist eine Lösung, die möglich, aber NICHT EMPFOHLEN ist!

Die folgende Lösung wird mit Xcode 6.2, SWIFT 1.1 kompiliert (wenn sich alle Klassen in unterschiedlichen Swift-Dateien befinden), sollte jedoch vermieden werden, da dies zu unerwartetem Verhalten führen kann (einschließlich eines Absturzes, insbesondere bei Verwendung nicht optionaler Typen). HINWEIS: DIESES FUNKTIONIERT NICHT MIT XCODE 6.3 BETA 3, SWIFT 1.2

class Chassis {}
class RacingChassis : Chassis {}
class Car {
    var chassis:Chassis? = Chassis()
}

class RaceCar: Car {
    override var chassis: RacingChassis? {
        get {
            return super.chassis as? RacingChassis
        }
        set {
            super.chassis = newValue
        }
    }
}
Lukman
quelle
1
Es funktioniert nicht für Swift 2.0. Es gibt "kann die veränderbare Eigenschaft" Chassis "vom Typ" Chassis nicht überschreiben? " mit kovariantem Typ 'RacingChassis?' "
mohamede1945
2

Theoretisch darfst du das so machen ...

class ViewController {

    var view: UIView! { return _view }

    private var _view: UIView!
}

class ScrollView : UIView {}

class ScrollViewController : ViewController {

    override var view: ScrollView! { return super.view as ScrollView! }
}

class HomeView : ScrollView {}

class HomeViewController : ScrollViewController {

    override var view: HomeView! { return super.view as HomeView! }
}

Dies funktioniert perfekt auf einem Xcode-Spielplatz.

Aber , wenn Sie versuchen , dies in einem realen Projekt, ein Compiler - Fehler sagt Ihnen:

Die 'Ansicht' der Deklaration kann nicht mehr als eine Deklaration der Oberklasse überschreiben

Ich habe bisher nur Xcode 6.0 GM überprüft.

Leider müssen Sie warten, bis Apple dies behebt.

Ich habe auch einen Fehlerbericht eingereicht. 18518795

Aleclarson
quelle
Dies ist eine interessante Lösung und funktioniert in 6.1.
Chris Conover
1

Ich habe viele Gründe gesehen, warum das Entwerfen einer API mithilfe von Variablen anstelle von Funktionen problematisch ist und sich für mich wie eine Problemumgehung anfühlt, wenn ich berechnete Eigenschaften verwende. Es gibt gute Gründe, Ihre Instanzvariablen gekapselt zu halten. Hier habe ich ein Protokoll Automobile erstellt, dem Car entspricht. Dieses Protokoll verfügt über eine Zugriffsmethode, die ein Chassis-Objekt zurückgibt. Da das Auto dem entspricht, kann die RaceCar-Unterklasse es überschreiben und eine andere Chassis-Unterklasse zurückgeben. Dadurch kann die Car-Klasse auf eine Schnittstelle (Automobile) programmieren, und die RaceCar-Klasse, die sich mit RacingChassis auskennt, kann direkt auf die Variable _racingChassis zugreifen.

class Chassis {}
class RacingChassis: Chassis {}

protocol Automobile {
    func chassis() -> Chassis
}

class Car: Automobile {
    private var _chassis: Chassis

    init () {
        _chassis = Chassis()
    }

    func chassis() -> Chassis {
        return _chassis
    }
}

class RaceCar: Car {
    private var _racingChassis: RacingChassis

    override init () {
        _racingChassis = RacingChassis()
        super.init()
    }

    override func chassis() -> Chassis {
        return _racingChassis
    }
}

Ein weiteres Beispiel dafür, warum das Entwerfen einer API mithilfe von Variablen nicht funktioniert, ist, wenn Sie Variablen in einem Protokoll haben. Wenn Sie alle Protokollfunktionen in Erweiterungen aufteilen möchten, können Sie dies tun, außer dass gespeicherte Eigenschaften nicht in Erweiterungen platziert werden können und in der Klasse definiert werden müssen (damit diese kompiliert werden können, müssen Sie den Code auskommentieren AdaptableViewController-Klasse und entfernen Sie die Modusvariable aus der Erweiterung):

protocol Adaptable {
    var mode: Int { get set }
    func adapt()
}

class AdaptableViewController: UIViewController {
    // var mode = 0
}

extension AdaptableViewController: Adaptable {

    var mode = 0 // compiler error

    func adapt() {
        //TODO: add adapt code
    }
}

Der obige Code hat den folgenden Compilerfehler: "Erweiterungen haben möglicherweise keine gespeicherten Eigenschaften". So können Sie das obige Beispiel neu schreiben, damit alles im Protokoll in der Erweiterung getrennt werden kann, indem Sie stattdessen Funktionen verwenden:

protocol Adaptable {
    func mode() -> Int
    func adapt()
}

class AdaptableViewController: UIViewController {
}

extension AdaptableViewController: Adaptable {
    func mode() -> Int {
        return 0
    }
    func adapt() {
        // adapt code
    }
}
Korey Hinton
quelle
1

Sie können es mit Generika erreichen:

class Descriptor {
    let var1 = "a"
}

class OtherDescriptor: Descriptor {
    let var2 = "b"
}

class Asset<D: Descriptor> {
    let descriptor: D

    init(withDescriptor descriptor: D) {
        self.descriptor = descriptor
    }

    func printInfo() {
        print(descriptor.var1)
    }
}

class OtherAsset<D: OtherDescriptor>: Asset<D> {
    override func printInfo() {
        print(descriptor.var1, descriptor.var2)
    }
}

let asset = Asset(withDescriptor: Descriptor())
asset.printInfo() // a

let otherAsset = OtherAsset(withDescriptor: OtherDescriptor())
otherAsset.printInfo() // a b

Mit diesem Ansatz erhalten Sie 100% sicheren Code ohne erzwungenes Auspacken.

Aber dass dies eine Art Hack ist und wenn Sie mehrere Eigenschaften neu definieren müssen, dann sehen Ihre Klassendeklarationen wie ein totales Chaos aus. Seien Sie also vorsichtig mit diesem Ansatz.

Al Zonke
quelle
1

Je nachdem, wie Sie die Eigenschaft verwenden möchten, verwenden Sie am einfachsten einen optionalen Typ für Ihre Unterklasse und überschreiben die didSet {}Methode für die Super:

class Chassis { }
class RacingChassis: Chassis { }

class Car {
    // Declare this an optional type, and do your 
    // due diligence to check that it's initialized
    // where applicable
    var chassis: Chassis?
}
class RaceCar: Car {
    // The subclass is naturally an optional too
    var racingChassis: RacingChassis?
    override var chassis: Chassis {
        didSet {
            // using an optional, we try to set the type
            racingChassis = chassis as? RacingChassis
        }
    }
}

Natürlich müssen Sie einige Zeit damit verbringen, zu überprüfen, ob die Klassen auf diese Weise initialisiert werden können. Wenn Sie jedoch die Eigenschaften auf optional setzen, schützen Sie sich vor Situationen, in denen das Casting nicht mehr funktioniert.

Brandonscript
quelle
0

Sie können einfach eine andere Variable von RacingChassis erstellen.

class Chassis {}
class RacingChassis : Chassis {}
class Car {
    let chassis: Chassis
    init(){
        chassis = Chassis()
}}

class RaceCar: Car {
let raceChassis: RacingChassis
init(){
        raceChassis = RacingChassis()
}}
Anton
quelle
Dies funktioniert, aber ich denke, Dash's Antwort geht noch einen Schritt weiter, indem sie überschreibt chassisund es uns ermöglicht, unsere RacingChassisInstanz mit der chassisEigenschaft abzurufen / festzulegen.
James
0

Versuche dies:

class Chassis {}
class RacingChassis : Chassis {}
class SuperChassis : RacingChassis {}

class Car {
    private var chassis: Chassis? = nil
    func getChassis() -> Chassis? {
        return chassis
    }

    func setChassis(chassis: Chassis) {
        self.chassis = chassis
    }
}

class RaceCar: Car {
    private var chassis: RacingChassis {
        get {
            return getChassis() as! RacingChassis
        }
        set {
            setChassis(chassis: newValue)
        }
    }

    override init() {
        super.init()

        chassis = RacingChassis()
    }
}

class SuperCar: RaceCar {
    private var chassis: SuperChassis {
        get {
            return getChassis() as! SuperChassis
        }
        set {
            setChassis(chassis: newValue)
        }
    }

    override init() {
        super.init()

        chassis = SuperChassis()
    }
}
Gebratener Reis
quelle
0
class Chassis {}
class RacingChassis : Chassis {}

class Car {
    fileprivate let theChassis: Chassis
    var chassis: Chassis {
        get {
            return theChassis
        }
    }
    fileprivate init(_ chassis: Chassis) {
        theChassis = chassis
    }
    convenience init() {
        self.init(Chassis())
    }
}
class RaceCar: Car {
    override var chassis: RacingChassis {
        get {
            return theChassis as! RacingChassis
        }
    }
    init() {
        super.init(RacingChassis())
    }
}
Dombi Bence
quelle
0

Im Folgenden kann ein einzelnes Objekt in Basis- und abgeleiteten Klassen verwendet werden. Verwenden Sie in der abgeleiteten Klasse die Eigenschaft für abgeleitete Objekte.

class Car {
    var chassis:Chassis?

    func inspect() {
        chassis?.checkForRust()
    }
}

class RaceCar: Car {
    var racingChassis: RacingChassis? {
        get {
            return chassis as? RacingChassis
        } 
    }

    override func inspect() {
        super.inspect()
        racingChassis?.tuneSuspension()
    }
}
Stickley
quelle
0

Eine leichte Variation anderer Antworten, aber einfacher und sicherer mit einigen netten Vorteilen.

class Chassis {}
class RacingChassis : Chassis {}

class Car {
    let chassis = Chassis()
}
class RaceCar: Car {
    var racingChassis: RacingChassis? {
        get {
            return chassis as? RacingChassis
        }
    }
}

Zu den Vorteilen gehört, dass es keine Einschränkung für das Chassis gibt (var, let, optional usw.) und es einfach ist, RaceCar in Unterklassen zu unterteilen. Unterklassen von RaceCar können dann ihren eigenen berechneten Wert für Chassis (oder RacingChassis) haben.

Ryan H.
quelle
-2

Legen Sie einfach eine neue imageview-Eigenschaft mit einer anderen Namenskonvention wie imgview fest, da imageView bereits eine eigene Eigenschaft ist und wir keine zwei starken Eigenschaften zuweisen können.

Ali Rafiq
quelle