Anulando la propiedad de superclase con diferentes tipos en Swift

129

En Swift, ¿alguien puede explicar cómo anular una propiedad en una superclase con otro objeto subclasificado de la propiedad original?

Tome este simple ejemplo:

class Chassis {}
class RacingChassis : Chassis {}

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

Esto da el error:

Cannot override with a stored property 'chassis'

Si tengo el chasis como 'var' en su lugar, me sale el error:

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

Lo único que pude encontrar en la guía en "Anulación de propiedades" indica que tenemos que anular el captador y el definidor, que pueden funcionar para cambiar el valor de la propiedad (si es 'var'), pero ¿qué pasa con el cambio de la clase de propiedad? ?

James
fuente

Respuestas:

115

Swift no le permite cambiar el tipo de clase de ninguna variable o propiedad. En su lugar, puede crear una variable adicional en la subclase que maneja el nuevo tipo de clase:

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

Parece que uno no puede declarar una propiedad con la sintaxis let y anularla con var en su subclase o viceversa, lo que puede deberse a que la implementación de la superclase podría no esperar que esa propiedad cambie una vez inicializada. Entonces, en este caso, la propiedad también debe declararse con 'var' en la superclase para que coincida con la subclase (como se muestra en el fragmento anterior). Si uno no puede cambiar el código fuente en la superclase, entonces probablemente sea mejor destruir el RaceCar actual y crear un nuevo RaceCar cada vez que el chasis necesita ser mutado.

Guión
fuente
1
Lo siento, acabo de eliminar mi comentario y luego vi tu respuesta. Estaba teniendo un problema porque en mi caso real mi superclase es una clase Objective-C con una strongpropiedad y recibí un error al intentar anularlo, pero parece que me perdí que se traduce en un "opcional implícitamente sin envolver" ( chassis!) en Rápido, así override var chassis : Chassis!que lo arregla.
James
44
Esto ya no funciona con Swift 1.1 en Xcode 6.1. Genera error: "No se puede anular la propiedad inmutable 'dejar' propiedad 'chasis' con el captador de una 'var'". ¿Alguna idea para una mejor solución?
Darrarski
1
Además, ya no funciona en Swift 1.2 en Xcode 6.3 beta. No se puede anular la propiedad mutable 'A' del tipo 'Tipo1' con el tipo covariante 'Tipo2'
bubuxu
10
Esto es realmente lamentable, y una falla de Swift, en mi opinión. Como RacingChassis es un chasis, el compilador no debería tener problemas para permitirle refinar la clase de la propiedad en una subclase. Muchos idiomas lo permiten, y no admitirlo conduce a soluciones feas como esta. Sin ofender. : /
devios1
1
Los opcionales proporcionan un medio para sugerir que una variable puede no estar presente porque fue proporcionada por una función que ya no devuelve el valor esperado. Tales casos pueden suceder si se anuló la función. Mira mi respuesta para más detalles.
10

Esto parece funcionar

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
fuente
1
Esto funciona ya que la propiedad del chasis se define como leten la Carclase, lo que hace que sea imposible cambiarla. Solo podemos cambiar la chassispropiedad en la RaceCarclase.
David Arve
44
Esto no funciona para Swift 2.0. Da "error: no puede anular la propiedad inmutable 'dejar' propiedad 'chasis' con el captador de una 'var'"
mohamede1945
Esto tampoco funciona en swift 4.0. falló la ejecución de layground: error: MyPlaygrounds.playground: 14: 15: error: no se puede anular el chasis inmutable 'let' property 'con el captador de un' var 'anular el chasis var: RacingChassis {^ MyPlaygrounds.playground: 11: 6: nota : intenta anular la propiedad aquí let chassis = Chassis () ^
karim
7

Prueba esto:

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

Luego:

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

Detalle en http://www.mylonly.com/14957025459875.html

adimtxg0422
fuente
Ah ordenado y limpio
Inder Kumar Rathore
3

La solución Dash proporcionada funciona bien, excepto que la superclase debe declararse con la palabra clave let en lugar de var. ¡Aquí hay una solución que es posible pero NO RECOMENDADA!

La solución a continuación se compilará con Xcode 6.2, SWIFT 1.1 (si todas las clases están en diferentes archivos swift), pero debe evitarse porque PUEDE CONDUCIR A COMPORTAMIENTOS INESPERADOS (INCLUYENDO UN ACCIDENTE, especialmente cuando se usan tipos no opcionales). NOTA: ESTO NO FUNCIONA 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
        }
    }
}
Lukman
fuente
1
No funciona para Swift 2.0. Da "no se puede anular la propiedad mutable 'chasis' del tipo 'Chasis?' con el tipo covariante 'RacingChassis?' "
mohamede1945
2

Teóricamente, puedes hacerlo de esta manera ...

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

Esto funciona perfectamente en un patio de juegos Xcode.

Pero , si intenta esto en un proyecto real, un error del compilador le dice:

La "vista" de la declaración no puede anular más de una declaración de superclase

Solo he verificado Xcode 6.0 GM a partir de ahora.

Desafortunadamente, tendrás que esperar hasta que Apple arregle esto.

También he enviado un informe de error. 18518795

aleclarson
fuente
Esta es una solución interesante, y funciona en 6.1.
Chris Conover
1

He visto muchas razones por las que diseñar una API usando variables en lugar de funciones es problemático y para mí el uso de propiedades calculadas se siente como una solución alternativa. Hay buenas razones para mantener sus variables de instancia encapsuladas. Aquí he creado un protocolo de automóvil con el que se ajusta el automóvil. Este protocolo tiene un método de acceso que devuelve un objeto Chassis. Dado que Car lo cumple, la subclase RaceCar puede anularlo y devolver una subclase de Chasis diferente. Esto permite que la clase Car programe en una interfaz (Automóvil) y la clase RaceCar que conoce sobre RacingChassis puede acceder directamente a 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
    }
}

Otro ejemplo de por qué el diseño de una API usando variables se desglosa es cuando tienes variables en un protocolo. Si desea dividir todas las funciones del protocolo en extensiones, puede hacerlo, excepto que las propiedades almacenadas no se pueden colocar en extensiones y deben definirse en la clase (para obtener esto para compilar, debe descomentar el código en AdaptableViewController clase y eliminar la variable de modo de la extensión):

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

El código anterior tendrá este error de compilación: "Las extensiones pueden no tener propiedades almacenadas". Así es como puede volver a escribir el ejemplo anterior para que todo en el protocolo pueda separarse en la extensión mediante el uso de funciones:

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

class AdaptableViewController: UIViewController {
}

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

Puede lograrlo con el uso de genéricos:

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 este enfoque, tendrá un código seguro de tipo 100% sin desenredos forzados.

Pero eso es una especie de truco, y si necesita redefinir varias propiedades, las declaraciones de clase se verán como un desastre total. Así que ten cuidado con este enfoque.

Al Zonke
fuente
1

Dependiendo de cómo planee usar la propiedad, la forma más sencilla de hacerlo es usar un tipo opcional para su subclase y anular el didSet {}método para el 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
        }
    }
}

Obviamente, debe pasar algún tiempo comprobando para asegurarse de que las clases se puedan inicializar de esta manera, pero al establecer las propiedades como opcionales, se protege contra situaciones en las que el lanzamiento ya no funciona.

Brandonscript
fuente
0

Simplemente puede crear otra 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
fuente
Esto funciona, pero creo que la respuesta de Dash va un paso más allá al anular chassisy permitirnos obtener / establecer nuestra RacingChassisinstancia con la chassispropiedad.
James
0

Prueba esto:

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()
    }
}
Arroz frito
fuente
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
fuente
0

Lo siguiente permite que se use un solo objeto en clases base y derivadas. En la clase derivada, use la propiedad del objeto derivado.

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
fuente
0

Una ligera variación de otras respuestas, pero más simple y seguro con algunos buenos beneficios.

class Chassis {}
class RacingChassis : Chassis {}

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

Los beneficios incluyen que no hay restricción sobre qué chasis debe ser (var, let, opcional, etc.), y es fácil subclasificar RaceCar. Las subclases de RaceCar pueden tener su propio valor calculado para el chasis (o racingChassis).

Ryan H
fuente
-2

simplemente configure una nueva propiedad de imageview con una convención de nomenclatura diferente como imgview porque imageView ya es su propia propiedad y no podemos asignar 2 propiedades fuertes.

Ali Rafiq
fuente