Remplacement de la propriété de superclasse avec un type différent dans Swift

129

Dans Swift, quelqu'un peut-il expliquer comment remplacer une propriété sur une superclasse avec un autre objet sous-classé de la propriété d'origine?

Prenons cet exemple simple:

class Chassis {}
class RacingChassis : Chassis {}

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

Cela donne l'erreur:

Cannot override with a stored property 'chassis'

Si j'ai un châssis comme 'var' à la place, j'obtiens l'erreur:

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

La seule chose que j'ai pu trouver dans le guide sous "Propriétés de remplacement" indique que nous devons remplacer le getter et le setter, ce qui peut fonctionner pour changer la valeur de la propriété (si c'est 'var'), mais qu'en est-il de la modification de la classe de propriété ?

James
la source

Réponses:

115

Swift ne vous permet pas de modifier le type de classe des variables ou propriétés. Au lieu de cela, vous pouvez créer une variable supplémentaire dans la sous-classe qui gère le nouveau type de 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")
            }
        }
    }
}

Il semble qu'on ne puisse pas déclarer une propriété avec la syntaxe let et la remplacer par var dans sa sous-classe ou vice-versa, ce qui peut être dû au fait que l'implémentation de la superclasse pourrait ne pas s'attendre à ce que cette propriété change une fois initialisée. Donc, dans ce cas, la propriété doit également être déclarée avec 'var' dans la superclasse pour correspondre à la sous-classe (comme indiqué dans l'extrait ci-dessus). Si l'on ne peut pas changer le code source de la superclasse, il est probablement préférable de détruire la RaceCar actuelle et de créer une nouvelle RaceCar à chaque fois que le châssis doit subir une mutation.

Tiret
la source
1
Désolé, je viens de supprimer mon commentaire, puis j'ai vu votre réponse. J'avais un problème parce que dans mon cas réel, ma superclasse est une classe Objective-C avec une strongpropriété et j'obtenais une erreur en essayant de la remplacer - mais il semble que j'ai manqué que cela se traduit par un "optionnel implicitement déballé" ( chassis!) dans Swift, donc override var chassis : Chassis!corrige ça.
James
4
Cela ne fonctionne plus avec Swift 1.1 sous Xcode 6.1. Génère une erreur: «Impossible de remplacer le châssis immuable de la propriété 'let' 'avec le getter d'un' var '». Des idées pour une meilleure solution?
Darrarski
1
Ne fonctionne plus non plus dans Swift 1.2 sous Xcode 6.3 beta. Impossible de remplacer la propriété mutable 'A' de type 'Type1' avec le type covariant 'Type2'
bubuxu
10
C'est vraiment nul, et un échec de Swift, imo. Puisque RacingChassis est un châssis, le compilateur ne devrait avoir aucun problème pour vous permettre d'affiner la classe de la propriété dans une sous-classe. De nombreux langages le permettent, et ne pas le prendre en charge conduit à de vilaines solutions de contournement comme celle-ci. Sans vouloir vous offenser. : /
devios1
1
Les options fournissent un moyen de suggérer qu'une variable peut ne pas être présente car elle a été fournie par une fonction qui ne renvoie plus la valeur attendue. De tels cas peuvent se produire si la fonction a été remplacée. Jetez un œil à ma réponse pour plus de détails.
10

Cela semble fonctionner

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
la source
1
Cela fonctionne car la propriété du châssis est définie comme letdans la Carclasse, ce qui rend impossible la modification. Nous ne pouvons modifier la chassispropriété que sur la RaceCarclasse.
David Arve
4
Cela ne fonctionne pas pour Swift 2.0. Il donne une "erreur: impossible de remplacer le châssis immuable de propriété 'let' avec le getter d'un 'var'"
mohamede1945
Cela ne fonctionne pas non plus dans Swift 4.0. échec de l'exécution de layground: erreur: MyPlaygrounds.playground: 14:15: erreur: impossible de remplacer le châssis immuable de la propriété 'let' avec le getter d'un châssis var de remplacement 'var': RacingChassis {^ MyPlaygrounds.playground: 11: 6: note : essayez de remplacer la propriété ici let chassis = Chassis () ^
karim
7

Essaye ça:

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

Ensuite:

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

Détails sur http://www.mylonly.com/14957025459875.html

adimtxg0422
la source
Ah neat and clean
Inder Kumar Rathore
3

La solution Dash fournie fonctionne bien, sauf que la super classe doit être déclarée avec le mot-clé let plutôt que var. Voici une solution possible mais NON RECOMMANDÉE!

La solution ci-dessous compilera avec Xcode 6.2, SWIFT 1.1 (si toutes les classes sont dans des fichiers swift différents) mais doit être évitée car ELLE PEUT ENTRAÎNER DES COMPORTEMENTS INATTENDUS (INCLUANT UN CRASH, en particulier lors de l'utilisation de types non optionnels). REMARQUE: CELA NE FONCTIONNE PAS AVEC 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
la source
1
Cela ne fonctionne pas pour Swift 2.0. Il donne "ne peut pas remplacer la propriété mutable 'chassis' de type 'Chassis?' avec le type covariant 'RacingChassis?' "
mohamede1945
2

Théoriquement, vous êtes autorisé à le faire de cette façon ...

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

Cela fonctionne parfaitement dans un terrain de jeu Xcode.

Mais , si vous essayez ceci dans un vrai projet, une erreur du compilateur vous indique:

La déclaration 'view' ne peut pas remplacer plus d'une déclaration de superclasse

Je n'ai vérifié que Xcode 6.0 GM pour le moment.

Malheureusement, vous devrez attendre qu'Apple corrige ce problème.

J'ai également soumis un rapport de bogue. 18518795

aleclarson
la source
C'est une solution intéressante, et cela fonctionne en 6.1.
Chris Conover
1

J'ai vu de nombreuses raisons pour lesquelles la conception d'une API utilisant des variables au lieu de fonctions est problématique et pour moi, l'utilisation de propriétés calculées ressemble à une solution de contournement. Il existe de bonnes raisons de garder vos variables d'instance encapsulées. Ici, j'ai créé un protocole Automobile auquel Car se conforme. Ce protocole a une méthode d'accesseur qui renvoie un objet Chassis. Puisque Car s'y conforme, la sous-classe RaceCar peut la remplacer et renvoyer une sous-classe Châssis différente. Cela permet à la classe Car de programmer sur une interface (Automobile) et la classe RaceCar qui connaît RacingChassis peut accéder directement à la variable _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 autre exemple de la raison pour laquelle la conception d'une API à l'aide de variables se décompose est lorsque vous avez des variables dans un protocole. Si vous souhaitez décomposer toutes les fonctions de protocole en extensions, vous pouvez, sauf que les propriétés stockées ne peuvent pas être placées dans des extensions et doivent être définies dans la classe (pour que cela compile, vous devez décommenter le code dans AdaptableViewController et supprimez la variable mode de l'extension):

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

Le code ci-dessus aura cette erreur de compilation: «Les extensions peuvent ne pas avoir de propriétés stockées». Voici comment vous pouvez réécrire l'exemple ci-dessus afin que tout dans le protocole puisse être séparé dans l'extension en utilisant des fonctions à la place:

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

class AdaptableViewController: UIViewController {
}

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

Vous pouvez y parvenir avec l'utilisation de génériques:

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

Avec cette approche, vous aurez un code de sécurité de type 100% sans forcer le déballage.

Mais c'est une sorte de hack, et si vous avez besoin de redéfinir plusieurs propriétés, les déclarations de classe ressembleront à un désordre total. Soyez donc prudent avec cette approche.

Al Zonke
la source
1

En fonction de la façon dont vous prévoyez d'utiliser la propriété, le moyen le plus simple de le faire est d'utiliser un type facultatif pour votre sous-classe et de remplacer la didSet {}méthode pour la 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
        }
    }
}

Évidemment, vous devez passer un peu de temps à vérifier pour vous assurer que les classes peuvent être initialisées de cette façon, mais en définissant les propriétés sur facultatif, vous vous protégez contre les situations où la diffusion ne fonctionne plus.

brandonscript
la source
0

Vous pouvez simplement créer une autre variable de RacingChassis.

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

class RaceCar: Car {
let raceChassis: RacingChassis
init(){
        raceChassis = RacingChassis()
}}
Anton
la source
Cela fonctionne, mais je pense que la réponse de Dash va encore plus loin en remplaçant chassiset en nous permettant d'obtenir / définir notre RacingChassisinstance avec la chassispropriété.
James
0

Essaye ça:

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()
    }
}
Riz sauté
la source
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
la source
0

Ce qui suit permet d'utiliser un seul objet dans les classes de base et dérivées. Dans la classe dérivée, utilisez la propriété d'objet dérivée.

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
la source
0

Une légère variation d'autres réponses, mais plus simples et plus sûres avec quelques avantages intéressants.

class Chassis {}
class RacingChassis : Chassis {}

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

Les avantages incluent qu'il n'y a aucune restriction sur ce que le châssis doit être (var, let, facultatif, etc.), et il est facile de sous-classer RaceCar. Les sous-classes de RaceCar peuvent alors avoir leur propre valeur calculée pour le châssis (ou racingChassis).

Ryan H
la source
-2

il suffit de définir une nouvelle propriété imageview avec une convention de dénomination différente comme imgview car imageView est déjà sa propre propriété et nous ne pouvons pas attribuer 2 propriétés fortes.

Ali Rafiq
la source