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!

Zaxonov
fonte
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
1
Possível duplicado da Swift, propriedade interna readwrite somente leitura externa
Beau Nouvelle

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.

Ethan
fonte
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.

elitalon
fonte
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.

mate
fonte
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++
    }
}
Arquivo Analógico
fonte
"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