Propriedades de variável somente leitura e não computadas no Swift


126

Estou tentando descobrir algo com a nova linguagem Apple Swift. Digamos que eu costumava fazer algo como o seguinte no Objective-C. Eu tenho readonlypropriedades e elas não podem ser alteradas individualmente. No entanto, usando um método específico, as propriedades são alteradas de maneira lógica.

Tomo o exemplo a seguir, um relógio muito simples. Eu escreveria isso em 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 uma finalidade específica, não podemos tocar diretamente os segundos, minutos e horas, e só é permitido incrementar segundo a segundo usando um método. Somente esse método pode alterar os valores usando o truque das variáveis ​​de instância.

Como não existem essas coisas no Swift, estou tentando encontrar um equivalente. Se eu fizer isso:

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

Isso funcionaria, mas qualquer um poderia alterar diretamente as propriedades.

Talvez eu já tenha um design ruim no Objective-C e é por isso que o potencial novo equivalente do Swift não faz sentido. Se não e se alguém tiver uma resposta, ficaria muito grato;)

Talvez o futuro mecanismo de controle de acesso prometido pela Apple seja a resposta?

Obrigado!


2
Controle de acesso é a resposta. Você criaria propriedades computadas somente leitura, enquanto usaria propriedades particulares para os dados reais.
21730 Leo Natan

Certifique-se de abrir um relatório de bug (aprimoramento) para informar à Apple o quanto isso está faltando.
Leo Natan

1
Para novos usuários: Role até a parte inferior ...
Gustaf Rosenblad

2
Marque a resposta de @ Ethan como correta.
Phatmann

Respostas:


356

Simplesmente prefixe a declaração de propriedade com private(set):

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

privatemantém local para um arquivo de origem, enquanto internallocal para o módulo / projeto.

private(set)cria uma read-onlypropriedade, enquanto privatedefine ambos sete getcomo privado.


28
"private" para mantê-lo local em um arquivo de origem, "interno" para mantê-lo local no módulo / projeto. private (set) apenas torna o conjunto privado. Privado faz tanto set e get funções privada
user965972

3
O primeiro comentário deve ser adicionado a esta resposta para torná-la mais completa.
Rob Norback 28/03

No momento (Swift 3.0.1), os níveis de controle de acesso foram alterados: a declaração "fileprivate" pode ser acessada apenas por código no mesmo arquivo de origem da declaração ”. A declaração "privada" pode ser acessada apenas por código dentro do escopo imediato da declaração.
Jurasic

20

Existem duas maneiras básicas de fazer o que você deseja. A primeira maneira é ter uma propriedade privada e uma propriedade pública computada que retorne essa propriedade:

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

Mas isso pode ser alcançado de uma maneira diferente e mais curta, conforme indicado na seção "Controle de acesso" do livro "A linguagem de programação Swift" :

public class Clock {

    public private(set) var hours = 0

}

Como uma observação lateral, você DEVE fornecer um inicializador público ao declarar um tipo público. Mesmo se você fornecer valores padrão para todas as propriedades, init()deverá ser definido explicitamente público:

public class Clock {

    public private(set) var hours = 0

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

}

Isso também é explicado na mesma seção do livro, ao falar sobre inicializadores padrão.


5

Como não há controles de acesso (o que significa que você não pode fazer um contrato de acesso que seja diferente dependendo de quem é o chamador), eis o que eu faria por enquanto:

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

Isso apenas adiciona um nível de indireção, por assim dizer, ao acesso direto ao balcão; outra classe pode criar um relógio e acessar o contador diretamente. Mas a idéia - ou seja, o contrato que estamos tentando fazer - é que outra classe use apenas as propriedades e métodos de nível superior do Clock. Não podemos impor esse contrato, mas, na verdade, era praticamente impossível impor também no Objective-C.


Parece que isso mudou desde então, correto? developer.apple.com/swift/blog/?id=5
Dan Rosenstark

1
@ Sim, com certeza, toda essa pergunta / resposta foi escrita muito antes de o controle de acesso ser adicionado ao Swift. E Swift ainda está evoluindo, então as respostas continuarão desatualizadas.
18715 matt18

2

Na verdade, o controle de acesso (que ainda não existe no Swift) não é tão imposto quanto você pode imaginar no Objetivo C. As pessoas podem modificar suas variáveis ​​somente leitura diretamente, se realmente quiserem. Eles simplesmente não fazem isso com a interface pública da classe.

Você pode fazer algo semelhante no Swift (cortar e colar seu código, além de algumas modificações, eu não testei):

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 é igual ao que você possui no objetivo C, exceto que as propriedades armazenadas reais são visíveis na interface pública.

No swift, você também pode fazer algo mais interessante, o que também no Objective C, mas provavelmente é mais bonito no swift (editado no navegador, eu não testei):

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

"As pessoas podem modificar suas variáveis ​​somente leitura diretamente" - o que você quer dizer exatamente? Quão?
user1244109

Eu estava me referindo ao objetivo C. No objetivo C, você tem acesso dinâmico a tudo sobre uma classe por meio das APIs de tempo de execução do objetivo C.
Arquivo analógico

de alguma forma, entendi que você implicava isso rapidamente. Ficou curioso. Graças
user1244109
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.