Sostituzione della proprietà della superclasse con tipo diverso in Swift


129

In Swift, qualcuno può spiegare come sovrascrivere una proprietà su una superclasse con un altro oggetto subclassato dalla proprietà originale?

Prendi questo semplice esempio:

class Chassis {}
class RacingChassis : Chassis {}

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

Questo dà l'errore:

Cannot override with a stored property 'chassis'

Se invece ho lo chassis come 'var', ottengo l'errore:

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

L'unica cosa che ho trovato nella guida in "Sostituzione delle proprietà" indica che dobbiamo ignorare il getter e il setter, che potrebbero funzionare per cambiare il valore della proprietà (se è 'var'), ma per quanto riguarda la modifica della classe di proprietà ?

Risposte:


115

Swift non consente di modificare il tipo di classe di alcuna variabile o proprietà. Invece è possibile creare una variabile aggiuntiva nella sottoclasse che gestisce il nuovo tipo di classe:

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")
            }
        }
    }
}

Sembra che non si possa dichiarare una proprietà con la sintassi let e sovrascriverla con var nella sua sottoclasse o viceversa, il che potrebbe dipendere dal fatto che l'implementazione della superclasse potrebbe non aspettarsi che la proprietà cambi una volta inizializzata. Quindi in questo caso la proprietà deve essere dichiarata con 'var' anche nella superclasse per corrispondere alla sottoclasse (come mostrato nello snippet sopra). Se non è possibile modificare il codice sorgente nella superclasse, probabilmente è meglio distruggere l'attuale RaceCar e creare una nuova RaceCar ogni volta che il telaio deve essere mutato.


1
Scusa, ho appena eliminato il mio commento e ho visto la tua risposta. Stavo avendo un problema perché nel mio caso reale la mia superclasse è una classe Objective-C con una strongproprietà e stavo ricevendo un errore nel tentativo di sovrascriverlo - ma sembra che mi sia perso che si traduce in un "opzione implicitamente non scartata" ( chassis!) in Veloce, quindi override var chassis : Chassis!risolve il problema.
James,

4
Questo non funziona più con Swift 1.1 in Xcode 6.1. Genera errore: "Impossibile sovrascrivere lo chassis 'let' property 'immutabile con il getter di un' var '". Qualche idea per una soluzione migliore?
Darrarski,

1
Inoltre, non funziona più in Swift 1.2 con Xcode 6.3 beta. Impossibile sovrascrivere la proprietà mutabile 'A' di tipo 'Tipo1' con tipo covariante 'Tipo2'
bubuxu,

10
Questo è davvero zoppo, e un fallimento di Swift, imo. Poiché RacingChassis è un Chassis, il compilatore non dovrebbe avere problemi che consenta di affinare la classe della proprietà in una sottoclasse. Molte lingue lo consentono, e non supportarlo porta a brutte soluzioni come questa. Senza offesa. : /
devios1

1
Gli optionals forniscono un mezzo per suggerire che una variabile potrebbe non essere presente perché è stata fornita da una funzione che non restituisce più il valore atteso. Tali casi possono verificarsi se la funzione è stata ignorata. Dai un'occhiata alla mia risposta per i dettagli.

10

Questo sembra funzionare

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()

1
Questo funziona poiché la proprietà dello chassis è definita come letnella Carclasse che rende impossibile la modifica. Possiamo solo modificare la chassisproprietà sulla RaceCarclasse.
David Arve,

4
Questo non funziona per Swift 2.0. Dà "errore: impossibile sovrascrivere lo chassis 'let' property 'immutabile con il getter di un' var '"
mohamede1945

Questo non funziona anche in Swift 4.0. esecuzione del layground fallita: errore: MyPlaygrounds.playground: 14: 15: errore: impossibile sovrascrivere lo chassis 'let' property 'immutabile con il getter di un' var 'override var chassis: RacingChassis {^ MyPlaygrounds.playground: 11: 6: note : prova a sovrascrivere la proprietà qui lascia chassis = Chassis () ^
karim

7

Prova questo:

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
     } 
}

Poi:

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

Dettaglio in http://www.mylonly.com/14957025459875.html


Ah pulito e ordinato
Inder Kumar Rathore

3

La soluzione Dash fornita funziona bene tranne per il fatto che la superclasse deve essere dichiarata con la parola chiave let anziché var. Ecco una soluzione che è possibile ma NON CONSIGLIATA!

La soluzione seguente verrà compilata con Xcode 6.2, SWIFT 1.1 (se tutte le classi sono in diversi file rapidi) ma dovrebbe essere evitata perché PUO 'PORTARE A COMPORTAMENTI INASPETTATI (COMPRESO UN CRASH, specialmente quando si usano tipi non opzionali). NOTA: NON FUNZIONA CON 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
        }
    }
}

1
Non funziona per Swift 2.0. Dà "impossibile sovrascrivere" chassis "di proprietà mutabili di tipo" Chassis? " con il tipo covariante 'RacingChassis?' "
mohamede1945

2

Teoricamente, ti è permesso farlo in questo modo ...

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! }
}

Funziona perfettamente in un parco giochi Xcode.

Ma , se lo provi in ​​un progetto reale, un errore del compilatore ti dice:

La "vista" della dichiarazione non può sovrascrivere più di una dichiarazione di superclasse

Al momento ho controllato solo Xcode 6.0 GM.

Sfortunatamente, dovrai aspettare che Apple risolva questo problema.

Ho anche inviato una segnalazione di bug. 18518795


Questa è una soluzione interessante e funziona in 6.1.
Chris Conover,

1

Ho visto molte ragioni per cui progettare un'API usando le variabili invece delle funzioni è problematico e per me usare le proprietà calcolate mi sembra una soluzione alternativa. Vi sono buoni motivi per mantenere incapsulate le variabili dell'istanza. Qui ho creato un protocollo Automobile a cui Auto è conforme. Questo protocollo ha un metodo accessor che restituisce un oggetto Chassis. Poiché Car è conforme ad essa, la sottoclasse RaceCar può ignorarla e restituire una sottoclasse Chassis diversa. Ciò consente alla classe Car di programmare un'interfaccia (Automobile) e la classe RaceCar che conosce RacingChassis può accedere direttamente alla variabile _racingChassis.

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
    }
}

Un altro esempio del motivo per cui la progettazione di un'API usando le variabili si interrompe è quando si hanno variabili in un protocollo. Se desideri suddividere tutte le funzioni del protocollo in un'estensione che puoi, tranne che le proprietà memorizzate non possono essere inserite in estensioni e devono essere definite nella classe (per ottenere questo da compilare dovresti decommentare il codice in Classe AdaptableViewController e rimuovere la variabile mode dall'estensione):

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
    }
}

Il codice sopra riportato avrà questo errore del compilatore: "Le estensioni potrebbero non avere proprietà memorizzate". Ecco come puoi riscrivere l'esempio sopra in modo che tutto nel protocollo possa essere separato nell'estensione usando invece le funzioni:

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

class AdaptableViewController: UIViewController {
}

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

1

Puoi raggiungerlo con l'uso di generici:

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

Con questo approccio avrai un codice sicuro al 100% senza forzature da scartare.

Ma questo è un po 'un trucco, e se dovrai ridefinire diverse proprietà di quanto le tue dichiarazioni di classe sembreranno un casino totale. Quindi stai attento con questo approccio.


1

A seconda di come prevedi di utilizzare la proprietà, il modo più semplice per farlo è utilizzare un tipo opzionale per la tua sottoclasse e sovrascrivere il didSet {}metodo per il 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
        }
    }
}

Ovviamente devi dedicare un po 'di tempo a verificare che le classi possano essere inizializzate in questo modo, ma impostando le proprietà su facoltativo, ti proteggi da situazioni in cui il casting non funziona più.


0

Puoi semplicemente creare un'altra variabile di RacingChassis.

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

class RaceCar: Car {
let raceChassis: RacingChassis
init(){
        raceChassis = RacingChassis()
}}

Funziona, ma penso che la risposta di Dash faccia un ulteriore passo avanti sostituendo chassise permettendoci di ottenere / impostare la nostra RacingChassisistanza con la chassisproprietà.
James,

0

Prova questo:

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()
    }
}

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())
    }
}

0

Di seguito è possibile utilizzare un singolo oggetto nelle classi base e derivate. Nella classe derivata, utilizzare la proprietà dell'oggetto derivato.

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()
    }
}

0

Una leggera variazione di altre risposte, ma più semplice e sicura con alcuni benefici interessanti.

class Chassis {}
class RacingChassis : Chassis {}

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

I vantaggi includono che non ci sono restrizioni su quale telaio deve essere (var, let, optional, ecc.) Ed è facile sottoclassare RaceCar. Le sottoclassi di RaceCar possono quindi avere il proprio valore calcolato per il telaio (o il telaio racing).


-2

basta impostare una nuova proprietà imageview con una convenzione di denominazione diversa come imgview perché imageView è già una proprietà propria e non possiamo assegnare 2 proprietà forti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.