Proprietà variabili di sola lettura e non calcolate in Swift


126

Sto cercando di capire qualcosa con il nuovo linguaggio Apple Swift. Diciamo che in Objective-C facevo qualcosa di simile al seguente. Ho delle readonlyproprietà e non possono essere modificate individualmente. Tuttavia, utilizzando un metodo specifico, le proprietà vengono modificate in modo logico.

Prendo il seguente esempio, un orologio molto semplice. Lo scriverei in 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

Per uno scopo specifico, non possiamo toccare direttamente i secondi, i minuti e le ore ed è consentito incrementare solo secondo per secondo usando un metodo. Solo questo metodo potrebbe cambiare i valori usando il trucco delle variabili di istanza.

Dato che non ci sono cose del genere in Swift, sto cercando di trovare un equivalente. Se lo faccio:

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

Funzionerebbe, ma chiunque potrebbe cambiare direttamente le proprietà.

Forse avevo già un cattivo design in Objective-C ed è per questo che il potenziale nuovo equivalente di Swift non ha senso. In caso contrario e se qualcuno ha una risposta, sarei molto grato;)

Forse i futuri meccanismi di controllo degli accessi promessi da Apple sono la risposta?

Grazie!


2
Il controllo degli accessi è la risposta. Dovresti creare proprietà calcolate in sola lettura, mentre usi le proprietà private per i dati effettivi.
Leo Natan,

Assicurati di aprire una segnalazione di bug (miglioramento) per far sapere ad Apple quanto direttamente manca.
Leo Natan,

1
Per i nuovi utenti: scorrere verso il basso ...
Gustaf Rosenblad,

2
Contrassegna la risposta di @ Ethan come corretta.
Phatmann,

Risposte:


356

È sufficiente aggiungere il prefisso alla dichiarazione di proprietà private(set), in questo modo:

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

privatelo mantiene locale in un file sorgente, mentre lo internalmantiene locale nel modulo / progetto.

private(set)crea una read-onlyproprietà, mentre il privateset di entrambi, seted geta privati.


28
"privato" per mantenerlo locale in un file sorgente, "interno" per mantenerlo locale al modulo / progetto. privato (set) rende solo privato il set. Privato rende entrambe le funzioni set e get private
user965972,

3
Il primo commento dovrebbe essere aggiunto a questa risposta per renderlo più completo.
Rob Norback,

In questo momento (Swift 3.0.1) I livelli di controllo dell'accesso sono cambiati: la dichiarazione "fileprivate" è accessibile solo tramite il codice nello stesso file sorgente della dichiarazione ". La dichiarazione "privata" è accessibile solo tramite un codice che rientra nell'ambito di applicazione immediato della dichiarazione.
Jurasic,

20

Esistono due modi di base per fare ciò che desideri. Il primo modo è avere una proprietà privata e una proprietà calcolata pubblica che restituisce quella proprietà:

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

Ma ciò può essere ottenuto in un modo diverso e più breve, come indicato nella sezione "Controllo degli accessi" del libro "Il linguaggio di programmazione rapido" :

public class Clock {

    public private(set) var hours = 0

}

Come nota a margine, DEVI fornire un inizializzatore pubblico quando dichiari un tipo pubblico. Anche se si forniscono valori predefiniti a tutte le proprietà, init()devono essere definiti esplicitamente pubblici:

public class Clock {

    public private(set) var hours = 0

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

}

Questo è anche spiegato nella stessa sezione del libro, quando si parla di inizializzatori predefiniti.


5

Dal momento che non ci sono controlli di accesso (il che significa che non è possibile stipulare un contratto di accesso che varia a seconda di chi è il chiamante), ecco cosa farei per ora:

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

Ciò aggiunge semplicemente un livello di riferimento indiretto, per così dire, all'accesso diretto al contatore; un'altra classe può creare un orologio e quindi accedere direttamente al suo contatore. Ma l' idea - ovvero il contratto che stiamo cercando di stipulare - è che un'altra classe dovrebbe usare solo le proprietà e i metodi di livello superiore dell'orologio. Non possiamo far valere quel contratto, ma in realtà era praticamente impossibile farlo anche in Objective-C.


Sembra che questo sia cambiato da allora, giusto? developer.apple.com/swift/blog/?id=5
Dan Rosenstark,

1
@Yar certo, l'intera domanda / risposta è stata scritta molto prima che il controllo degli accessi fosse aggiunto a Swift. E Swift è ancora in evoluzione, quindi le risposte continueranno ad essere obsolete.
matt

2

In realtà il controllo dell'accesso (che non esiste ancora in Swift) non è così efficace come si potrebbe pensare nell'Obiettivo C. Le persone possono modificare direttamente le variabili di sola lettura, se lo desiderano. Semplicemente non lo fanno con l'interfaccia pubblica della classe.

Puoi fare qualcosa di simile in Swift (taglia e incolla il tuo codice, oltre ad alcune modifiche, non l'ho provato):

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

che è lo stesso di quello che hai nell'obiettivo C, tranne per il fatto che le proprietà memorizzate effettive sono visibili nell'interfaccia pubblica.

In swift puoi anche fare qualcosa di più interessante, cosa che puoi fare anche in Objective C, ma probabilmente è più carino in swift (modificato nel browser, non l'ho provato):

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

"Le persone possono modificare direttamente le variabili di sola lettura" - cosa intendi esattamente? Come?
user1244109

Mi riferivo all'obiettivo C. Nell'obiettivo C hai accesso dinamico a tutto ciò che riguarda una classe tramite le API di runtime dell'obiettivo C.
File analogico

in qualche modo ho capito che lo hai insinuato per un rapido. Sei curioso. Grazie
user1244109
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.