Propiedades variables de solo lectura y no calculadas en Swift

126

Estoy tratando de descubrir algo con el nuevo lenguaje Apple Swift. Digamos que solía hacer algo como lo siguiente en Objective-C. Tengo readonlypropiedades y no se pueden cambiar individualmente. Sin embargo, utilizando un método específico, las propiedades se cambian de forma lógica.

Tomo el siguiente ejemplo, un reloj muy simple. Escribiría esto en Objective-C.

@interface Clock : NSObject

@property (readonly) NSUInteger hours;
@property (readonly) NSUInteger minutes;
@property (readonly) NSUInteger seconds;

- (void)incrementSeconds;

@end

@implementation Clock

- (void)incrementSeconds {
     _seconds++;

     if (_seconds == 60) {
        _seconds = 0;
        _minutes++;

         if (_minutes == 60) {
            _minutes = 0;
            _hours++;
        }
    }
}

@end

Para un propósito específico, no podemos tocar los segundos, minutos y horas directamente, y solo se permite incrementar segundo por segundo usando un método. Solo este método podría cambiar los valores utilizando el truco de las variables de instancia.

Como no hay tales cosas en Swift, estoy tratando de encontrar un equivalente. Si hago esto:

class Clock : NSObject {

    var hours:   UInt = 0
    var minutes: UInt = 0
    var seconds: UInt = 0

    func incrementSeconds() {

        self.seconds++

        if self.seconds == 60 {

            self.seconds = 0
            self.minutes++

            if self.minutes == 60 {

                self.minutes = 0
                self.hours++
            }
        }
    }
}

Eso funcionaría, pero cualquiera podría cambiar directamente las propiedades.

Tal vez ya tenía un mal diseño en Objective-C y es por eso que el potencial nuevo equivalente de Swift no tiene sentido. Si no es así y si alguien tiene una respuesta, estaría muy agradecido;)

¿Quizás los futuros mecanismos de control de acceso prometidos por Apple son la respuesta?

¡Gracias!

Zaxonov
fuente
2
El control de acceso es la respuesta. Crearía propiedades calculadas de solo lectura, mientras usa propiedades privadas para los datos reales.
Leo Natan
Asegúrese de abrir un informe de error (mejora) para que Apple sepa cuán directamente falta esto.
Leo Natan
1
Para nuevos usuarios: Desplácese hasta el final ...
Gustaf Rosenblad
2
Marque la respuesta de @ Ethan como correcta.
phatmann

Respuestas:

356

Simplemente prefija la declaración de propiedad con private(set), así:

public private(set) var hours:   UInt = 0
public private(set) var minutes: UInt = 0
public private(set) var seconds: UInt = 0

privatelo mantiene local a un archivo fuente, mientras que lo internalmantiene local al módulo / proyecto.

private(set)crea una read-onlypropiedad, mientras privateestablece ambos, sety geten privado.

Ethan
fuente
28
"privado" para mantenerlo local en un archivo fuente, "interno" para mantenerlo local en el módulo / proyecto. privado (conjunto) solo hace que el conjunto sea privado. Private hace que las funciones set y get sean privadas
user965972
3
El primer comentario debe agregarse a esta respuesta para hacerlo más completo.
Rob Norback
En este momento (Swift 3.0.1) los niveles de control de acceso han cambiado: solo se puede acceder a la declaración "fileprivate" mediante el código en el mismo archivo fuente que la declaración ”. Solo se puede acceder a la declaración "privada" mediante un código dentro del alcance inmediato de la declaración.
Jurásico
20

Hay dos formas básicas de hacer lo que quieres. La primera forma es tener una propiedad privada y una propiedad pública computada que devuelva esa propiedad:

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

Pero esto se puede lograr de una manera diferente y más corta, como se indica en la sección "Control de acceso" del libro "El lenguaje de programación Swift" :

public class Clock {

    public private(set) var hours = 0

}

Como nota al margen, DEBE proporcionar un inicializador público al declarar un tipo público. Incluso si proporciona valores predeterminados para todas las propiedades, init()debe definirse explícitamente como público:

public class Clock {

    public private(set) var hours = 0

    public init() {
      hours = 0
    }
    // or simply `public init() {}`

}

Esto también se explica en la misma sección del libro, cuando se habla de inicializadores predeterminados.

elitalon
fuente
5

Dado que no hay controles de acceso (lo que significa que no puede hacer un contrato de acceso que difiera dependiendo de quién sea la persona que llama), esto es lo que haría por ahora:

class Clock {
    struct Counter {
        var hours = 0;
        var minutes = 0;
        var seconds = 0;
        mutating func inc () {
            if ++seconds >= 60 {
                seconds = 0
                if ++minutes >= 60 {
                    minutes = 0
                    ++hours
                }
            }
        }
    }
    var counter = Counter()
    var hours : Int { return counter.hours }
    var minutes : Int { return counter.minutes }
    var seconds : Int { return counter.seconds }
    func incrementTime () { self.counter.inc() }
}

Esto simplemente agrega un nivel de indirección, por así decirlo, al acceso directo al contador; otra clase puede hacer un reloj y luego acceder a su contador directamente. Pero la idea , es decir, el contrato que estamos tratando de hacer, es que otra clase solo use las propiedades y métodos de nivel superior del Reloj. No podemos hacer cumplir ese contrato, pero en realidad era prácticamente imposible hacerlo también en Objective-C.

mate
fuente
Parece que esto ha cambiado desde entonces, ¿correcto? developer.apple.com/swift/blog/?id=5
Dan Rosenstark
1
@ Seguro, toda esta pregunta / respuesta se escribió mucho antes de que se agregara el control de acceso a Swift. Y Swift todavía está evolucionando, por lo que las respuestas continuarán desactualizadas.
mate
2

En realidad, el control de acceso (que aún no existe en Swift) no se aplica tan bien como puede pensar en el Objetivo C. Las personas pueden modificar sus variables de solo lectura directamente, si realmente lo desean. Simplemente no lo hacen con la interfaz pública de la clase.

Puede hacer algo similar en Swift (cortar y pegar su código, más algunas modificaciones, no lo probé):

class Clock : NSObject {

    var _hours:   UInt = 0
    var _minutes: UInt = 0
    var _seconds: UInt = 0

    var hours: UInt {
    get {
      return _hours
    }
    }

    var minutes: UInt {
    get {
      return _minutes
    }
    }

    var seconds: UInt  {
    get {
      return _seconds
    }
    }

    func incrementSeconds() {

        self._seconds++

        if self._seconds == 60 {

            self._seconds = 0
            self._minutes++

            if self._minutes == 60 {

                self._minutes = 0
                self._hours++
            }
        }
    }
}

que es lo mismo que tiene en el Objetivo C, excepto que las propiedades reales almacenadas son visibles en la interfaz pública.

En swift también puedes hacer algo más interesante, que también puedes hacer en Objective C, pero probablemente sea más bonito en swift (editado en el navegador, no lo probé):

class Clock : NSObject {

    var hours: UInt = 0

    var minutes: UInt {
    didSet {
      hours += minutes / 60
      minutes %= 60
    }
    }

    var seconds: UInt  {
    didSet {
      minutes += seconds / 60
      seconds %= 60
    }
    }

    // you do not really need this any more
    func incrementSeconds() {
        seconds++
    }
}
Archivo Analógico
fuente
"La gente puede modificar sus variables de solo lectura directamente", ¿qué quiere decir exactamente? ¿Cómo?
user1244109
Me refería al objetivo C. En el objetivo C tiene acceso dinámico a todo lo relacionado con una clase a través de las API de tiempo de ejecución de Objective C.
Archivo analógico
De alguna manera entendí que lo implicabas para Swift. Tengo curiosidad Gracias
user1244109