Captadores y establecedores de propiedades

194

Con esta clase simple obtengo la advertencia del compilador

Intentando modificar / acceder xdentro de su propio setter / getter

y cuando lo uso así:

var p: point = Point()
p.x = 12

Me sale un EXC_BAD_ACCESS. ¿Cómo puedo hacer esto sin respaldo explícito de ivars?

class Point {

    var x: Int {
        set {
            x = newValue * 2 //Error
        }
        get {
            return x / 2 //Error
        }
    }
    // ...
}
Atomix
fuente
1
Hacerlo también supondría una carga adicional para el compilador y consumiría la CPU enérgicamente. Acabo de hacer eso: | . Este fue el error que estaba creando. (Estaba usando el patio de juegos)
Miel el
Este comportamiento es confuso, en lugar de usar lo setque quieres usar didSet. Las propiedades se comportan de manera diferente en Swift que Objective-C u otros lenguajes cuando se implementa set. Vea la respuesta a continuación de @jack y un ejemplo de didSet@cSquirrel
Paul Solt

Respuestas:

232

Setters y Getters se aplican a computed properties; tales propiedades no tienen almacenamiento en la instancia; el valor del captador debe calcularse a partir de otras propiedades de la instancia. En su caso, no hay xque asignarlo.

Explícitamente: "¿Cómo puedo hacer esto sin respaldar explícitamente los ivars"? No puede: necesitará algo para hacer una copia de seguridad de la propiedad calculada. Prueba esto:

class Point {
  private var _x: Int = 0             // _x -> backingX
  var x: Int {
    set { _x = 2 * newValue }
    get { return _x / 2 }
  }
}

Específicamente, en Swift REPL:

 15> var pt = Point()
pt: Point = {
  _x = 0
}
 16> pt.x = 10
 17> pt
$R3: Point = {
  _x = 20
}
 18> pt.x
$R4: Int = 10
GoZoner
fuente
106

Los setters / getters en Swift son bastante diferentes a ObjC. La propiedad se convierte en una propiedad calculada, lo que significa que no tiene una variable de respaldo _xcomo la que tendría en ObjC.

En el código de solución a continuación, puede ver xTimesTwoque no almacena nada, sino que simplemente calcula el resultado x.

Ver documentos oficiales sobre propiedades calculadas .

La funcionalidad que desea también podría ser Observadores de propiedades .

Lo que necesitas es:

var x: Int

var xTimesTwo: Int {
    set {
       x = newValue / 2
    }
    get {
        return x * 2
    }
}

Puede modificar otras propiedades dentro del setter / getters, que es para lo que están destinadas.

Jack
fuente
1
Sí, eso es lo que estoy leyendo en la documentación. Me salté la sección de propiedades y parece que no hay forma de evitar esto, ¿verdad?
Atomix
Sí, necesitará otra variable de instancia para "respaldar" la primera si realmente desea hacer esto. Sin embargo, los setters no están diseñados para este propósito, probablemente debería repensar lo que está tratando de lograr con esto.
Jack
55
puede usar lo didSetque le permitirá cambiar el valor inmediatamente después de configurarlo. Nada para el get aunque ...
ObjectiveCsam
1
NOTA: Si desea revertir el valor porque era una entrada no válida, necesitaría xvolver a establecerlo oldValueen didSet. Este cambio de comportamiento es muy confuso debido a las propiedades de Objective-C, ¡gracias!
Paul Solt
105

Puede personalizar el valor establecido utilizando el observador de propiedades. Para hacer esto, use 'didSet' en lugar de 'set'.

class Point {

var x: Int {
    didSet {
        x = x * 2
    }
}
...

En cuanto a getter ...

class Point {

var doubleX: Int {
    get {
        return x / 2
    }
}
...
c ardilla
fuente
¿Cómo podría inicializar xcon un valor predeterminado en este patrón?
i_am_jorf
3
Veo, sería: var x: Int = 0 { didSet { ....
i_am_jorf
31

Para elaborar sobre la respuesta de GoZoner:

Su problema real aquí es que está llamando recursivamente a su captador.

var x:Int
    {
        set
        {
            x = newValue * 2 // This isn't a problem
        }
        get {
            return x / 2 // Here is your real issue, you are recursively calling 
                         // your x property's getter
        }
    }

Como el comentario del código sugiere anteriormente, está llamando infinitamente al captador de la propiedad x, que continuará ejecutándose hasta que obtenga un código EXC_BAD_ACCESS (puede ver el control giratorio en la esquina inferior derecha del entorno del patio de su Xcode).

Considere el ejemplo de la documentación de Swift :

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct AlternativeRect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}

Observe cómo la propiedad calculada del centro nunca se modifica o devuelve en la declaración de la variable.

Abdullah Bakhsh
fuente
La regla del pulgar es que nunca acceder a la propiedad en sí desde dentro del captador es decir get. Porque eso desencadenaría otro getque desencadenaría otro. . . Ni siquiera lo imprimas. ¡Porque la impresión también requiere 'obtener' el valor antes de poder imprimirlo!
Miel
19

Para anular settery getterpara las variables rápidas, use el siguiente código dado

var temX : Int? 
var x: Int?{

    set(newX){
       temX = newX
    }

    get{
        return temX
    }
}

Necesitamos mantener el valor de variable en una variable temporal, ya que tratar de acceder a la misma variable cuyo anulador / establecedor se anulará dará como resultado bucles infinitos.

Podemos invocar al setter simplemente así

x = 10

Getter será invocado al disparar debajo de la línea de código dada

var newVar = x
Sebin Roy
fuente
8

Estás definiendo recursivamentex con x. ¿Como si alguien te preguntara cuántos años tienes? Y respondes "Tengo el doble de mi edad". Lo cual no tiene sentido.

Debe decir que tengo el doble de la edad de John o cualquier otra variable que no sea usted.

Las variables calculadas siempre dependen de otra variable.


La regla del pulgar es que nunca acceder a la propiedad en sí desde dentro del captador es decir get. Porque eso desencadenaría otro getque desencadenaría otro. . . Ni siquiera lo imprimas. ¡Porque la impresión también requiere 'obtener' el valor antes de poder imprimirlo!

struct Person{
    var name: String{
        get{
            print(name) // DON'T do this!!!!
            return "as"
        }
        set{
        }
    }
}

let p1 = Person()

Como eso daría la siguiente advertencia:

Intentando acceder al 'nombre' desde su propio getter.

El error se ve vago como este:

ingrese la descripción de la imagen aquí

Como alternativa, es posible que desee utilizar didSet. Con esto didSet, mantendrá el valor que se estableció anteriormente y que acaba de establecerse en. Para más ver esta respuesta .

Miel
fuente
8

Actualización: Swift 4

En el siguiente setter de clase y getter se aplica a la variable sideLength

class Triangle: {
    var sideLength: Double = 0.0

    init(sideLength: Double, name: String) { //initializer method
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }

    var perimeter: Double {
        get { // getter
            return 3.0 * sideLength
        }
        set(newValue) { //setter
            sideLength = newValue / 4.0
        }
   }

Creando objeto

var triangle = Triangle(sideLength: 3.9, name: "a triangle")

Adquiridor

print(triangle.perimeter) // invoking getter

Setter

triangle.perimeter = 9.9 // invoking setter
Saranjith
fuente
6

Intenta usar esto:

var x:Int!

var xTimesTwo:Int {
    get {
        return x * 2
    }
    set {
        x = newValue / 2
    }
}

Esta es básicamente la respuesta de Jack Wu, pero la diferencia es que en la respuesta de Jack Wu su variable x es var x: Int, en la mía, mi variable x es así: var x: Int!así que todo lo que hice fue convertirla en un tipo opcional.

Derrotador épico
fuente
1

Actualización para Swift 5.1

A partir de Swift 5.1 ahora puede obtener su variable sin usar la palabra clave get . Por ejemplo:

var helloWorld: String {
"Hello World"
}
atalayasa
fuente
Si trato de cambiar, helloWorld = "macWorld" , No se puede asignar al valor: 'helloWorld' es un error de propiedad de obtener solo se muestra.
McDonal_11
¿Es posible asignar nuevos valores? o no ?
McDonal_11
No creo que sea posible.
atalayasa
Entonces, literalmente, var helloWorld: String {"Hello World"} y, ¿qué helloWorld: String = "Hello World" son iguales?
McDonal_11
Sí, eso creo.
atalayasa
0

Los establecedores y captadores en Swift se aplican a las propiedades / variables calculadas. Estas propiedades / variables no se almacenan realmente en la memoria, sino que se calculan en función del valor de las propiedades / variables almacenadas.

Consulte la documentación de Swift de Apple sobre el tema: Declaraciones de variables de Swift .

jefe2000
fuente
0

Aquí hay una respuesta teórica. Eso se puede encontrar aquí

Una propiedad {get set} no puede ser una propiedad almacenada constante. Debe ser una propiedad calculada y se deben implementar get y set.

Talha Ahmad Khan
fuente