Anular una propiedad almacenada en Swift

126

Noté que el compilador no me deja anular una propiedad almacenada con otro valor almacenado (lo que parece extraño):

class Jedi {
    var lightSaberColor = "Blue"
}


class Sith: Jedi {
    override var lightSaberColor = "Red" // Cannot override with a stored property lightSaberColor
}

Sin embargo, se me permite hacer esto con una propiedad calculada:

class Jedi {
    let lightSaberColor = "Blue"
}


class Sith: Jedi {
    override var lightSaberColor : String{return "Red"}

}

¿Por qué no se me permite darle otro valor?

¿Por qué reemplazar una propiedad almacenada es una abominación y hacerlo con un kosher calculado? ¿Qué donde piensan?

cfischer
fuente
1
posible duplicado de propiedad de superclase
Max MacLeod

Respuestas:

82

¿Por qué no se me permite simplemente darle otro valor?

Definitivamente se le permite dar a una propiedad heredada un valor diferente. Puede hacerlo si inicializa la propiedad en un constructor que toma ese valor inicial y pasa un valor diferente de la clase derivada:

class Jedi {
    // I made lightSaberColor read-only; you can make it writable if you prefer.
    let lightSaberColor : String
    init(_ lsc : String = "Blue") {
        lightSaberColor = lsc;
    }
}

class Sith : Jedi {
    init() {
        super.init("Red")
    }
}

let j1 = Jedi()
let j2 = Sith()

println(j1.lightSaberColor)
println(j2.lightSaberColor)

Anular una propiedad no es lo mismo que darle un nuevo valor, es más como darle a una clase una propiedad diferente. De hecho, eso es lo que sucede cuando anula una propiedad calculada: el código que calcula la propiedad en la clase base se reemplaza por el código que calcula la anulación de esa propiedad en la clase derivada.

[¿Es posible] anular la propiedad almacenada real, es decir, lightSaberColorque tiene algún otro comportamiento?

Además de los observadores, las propiedades almacenadas no tienen comportamiento, por lo que realmente no hay nada que anular. Dar a la propiedad un valor diferente es posible a través del mecanismo descrito anteriormente. Esto hace exactamente lo que el ejemplo en la pregunta está tratando de lograr, con una sintaxis diferente.

dasblinkenlight
fuente
2
@MaxMacLeod Además de los observadores, las propiedades almacenadas no tienen comportamiento, por lo que realmente no hay nada que anular. Quería darle a una propiedad almacenada un valor diferente en la subclase, pero no estaba seguro sobre el mecanismo para lograrlo. La respuesta explica cómo se puede hacer en Swift. Lamento la respuesta tardía, parece que su comentario causa suficiente confusión como para atraer votos negativos, así que decidí explicar lo que está sucediendo.
dasblinkenlight
55

Para mí, su ejemplo no funciona en Swift 3.0.1.

Entré en el patio de recreo este código:

class Jedi {
    let lightsaberColor = "Blue"
}

class Sith: Jedi {
    override var lightsaberColor : String {
        return "Red"
    }
}

Lanza error en tiempo de compilación en Xcode:

no puede anular la propiedad 'let' inmutable 'lightsaberColor' con el captador de una 'var'

No, no puede cambiar el tipo de propiedad almacenada. El principio de sustitución de Liskov obliga a permitir que se use una subclase en un lugar donde se desee la superclase.

Pero si lo cambia vary, por lo tanto, agrega el seten la propiedad calculada, puede anular la propiedad almacenada con una propiedad calculada del mismo tipo.

class Jedi {
    var lightsaberColor = "Blue"
}


class Sith: Jedi {
    override var lightsaberColor : String {
        get {
            return "Red"
        }
        set {
            // nothing, because only red is allowed
        }
    }
}

Esto es posible porque puede tener sentido cambiar de propiedad almacenada a propiedad calculada.

Pero anule una varpropiedad almacenada con una propiedad almacenadavar propiedad no tiene sentido, porque solo puede cambiar el valor de la propiedad.

Sin embargo, no puede anular una propiedad almacenada con una propiedad almacenada en absoluto.


No diría que los Sith son Jedi :-P. Por lo tanto, está claro que esto no puede funcionar.

Binario
fuente
18
class SomeClass {
    var hello = "hello"
}
class ChildClass: SomeClass {
    override var hello: String {
        set {
            super.hello = newValue
        }
        get {
            return super.hello
        }    
    }
}
Zhiping Yang
fuente
13
Si bien este fragmento de código puede resolver la pregunta, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo la pregunta para los lectores en el futuro, y que esas personas podrían no conocer los motivos de su sugerencia de código.
DimaSan
15

Probablemente desee asignar otro valor a la propiedad:

class Jedi {
    var lightSaberColor = "Blue"
}


class Sith: Jedi {
    override init() {
        super.init()
        self.lightSaberColor = "Red"
    }
}
zisoft
fuente
9
lo mismo aplica según el comentario anterior
Max MacLeod
10

Para Swift 4, de la documentación de Apple :

Puede anular una instancia heredada o una propiedad de tipo para proporcionar su propio getter y setter personalizado para esa propiedad, o agregar observadores de propiedad para permitir que la propiedad anulante observe cuando cambia el valor de propiedad subyacente.

riyasavla
fuente
7

En Swift, desafortunadamente esto no es posible. La mejor alternativa es la siguiente:

class Jedi {
    private(set) var lightsaberColor = "Blue"
}


class Sith: Jedi {
    override var lightsaberColor : String {
        get {
            return "Red"
        }
    }
}
Noah Wilder
fuente
3

Tuve el mismo problema para establecer una constante para un controlador de vista.

Como estoy usando el generador de interfaces para administrar la vista, no puedo usar init(), por lo que mi solución fue similar a otras respuestas, excepto que usé una variable calculada de solo lectura en las clases base y heredadas.

class Jedi {
    var type: String {
        get { return "Blue" }
    }
}

class Sith: Jedi {
    override var type: String {
        get { return "Red" }
    }
}
Yassine ElBadaoui
fuente
3

Si intentas hacer eso en Swift 5 serás recibido por un

No se puede anular la propiedad inmutable 'let' 'lightSaberColor' con el captador de una 'var'

Su mejor apuesta es declararlo como una propiedad calculada.

Esto funciona ya que estamos anulando la get {}función

class Base {
   var lightSaberColor: String { "base" }
}

class Red: Base {
   override var lightSaberColor: String { "red" }
}
Hugo Alonso
fuente
2

Swift no le permite anular una variable stored propertyEn lugar de esto, puede usarcomputed property

class A {
    var property1 = "A: Stored Property 1"

    var property2: String {
        get {
            return "A: Computed Property 2"
        }
    }

    let property3 = "A: Constant Stored Property 3"

    //let can not be a computed property
    
    func foo() -> String {
        return "A: foo()"
    }
}

class B: A {

    //now it is a computed property
    override var property1: String {

        set { }
        get {
            return "B: overrode Stored Property 1"
        }
    }

    override var property2: String {
        get {
            return "B: overrode Computed Property 2"
        }
    }
    
    override func foo() -> String {
        return "B: foo()"
    }

    //let can not be overrode
}
func testPoly() {
    let a = A()
    
    XCTAssertEqual("A: Stored Property 1", a.property1)
    XCTAssertEqual("A: Computed Property 2", a.property2)
    
    XCTAssertEqual("A: foo()", a.foo())
    
    let b = B()
    XCTAssertEqual("B: overrode Stored Property 1", b.property1)
    XCTAssertEqual("B: overrode Computed Property 2", b.property2)
    
    XCTAssertEqual("B: foo()", b.foo())
    
    //B cast to A
    XCTAssertEqual("B: overrode Stored Property 1", (b as! A).property1)
    XCTAssertEqual("B: overrode Computed Property 2", (b as! A).property2)
    
    XCTAssertEqual("B: foo()", (b as! A).foo())
}

Es más claro cuando se compara con Java, donde un campo de clase no se puede anular y no admite polimorfismo porque se define en tiempo de compilación (se ejecuta de manera eficiente). Se llama ocultación de variables [Acerca de] No se recomienda utilizar esta técnica porque es difícil de leer / soportar

[Propiedad Swift]

yoAlex5
fuente
1

También puede usar una función para anular. No es una respuesta directa, pero puede enriquecer este tema)

Clase A

override func viewDidLoad() {
    super.viewDidLoad()

    if shouldDoSmth() {
       // do
    }
}

public func shouldDoSmth() -> Bool {
    return true
}

Clase B: A

public func shouldDoSmth() -> Bool {
    return false
}
Nik Kov
fuente