Properti variabel read-only dan non-computed di Swift

126

Saya mencoba mencari tahu sesuatu dengan bahasa Apple Swift yang baru. Katakanlah saya dulu melakukan sesuatu seperti yang berikut di Objective-C. Saya memiliki readonlyproperti, dan mereka tidak dapat diubah secara individual. Namun, menggunakan metode tertentu, properti diubah secara logis.

Saya ambil contoh berikut, jam yang sangat sederhana. Saya akan menulis ini di 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

Untuk tujuan tertentu, kami tidak dapat menyentuh detik, menit dan jam secara langsung, dan itu hanya diperbolehkan untuk meningkatkan detik demi detik menggunakan metode. Hanya metode ini yang dapat mengubah nilai dengan menggunakan trik variabel instan.

Karena tidak ada hal seperti itu di Swift, saya mencoba mencari yang setara. Jika saya melakukan ini:

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

Itu akan berhasil, tetapi siapa pun dapat mengubah properti secara langsung.

Mungkin saya sudah memiliki desain yang buruk di Objective-C dan itulah mengapa potensi Swift baru yang setara tidak masuk akal. Jika tidak dan jika seseorang memiliki jawaban, saya akan sangat berterima kasih;)

Mungkin Mekanisme Akses Kontrol di masa depan yang dijanjikan oleh Apple adalah jawabannya?

Terima kasih!

Zaxonov
sumber
2
Kontrol akses adalah jawabannya. Anda akan membuat properti yang dihitung hanya baca, sambil menggunakan properti pribadi untuk data aktual.
Leo Natan
Pastikan untuk membuka laporan bug (peningkatan) untuk memberi tahu Apple bagaimana sebenarnya hal ini hilang.
Leo Natan
1
Untuk pengguna baru: Gulir ke bawah ...
Gustaf Rosenblad
2
Harap tandai jawaban @ Ethan sebagai benar.
phatmann
1
Kemungkinan duplikat dari Swift readonly external, readwrite internal internal
Beau Nouvelle

Jawaban:

356

Cukup awali deklarasi properti dengan private(set), seperti:

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

privatemenyimpannya lokal ke file sumber, sementara internalmenyimpannya lokal ke modul / proyek.

private(set)menciptakan read-onlyproperti, sementara privatemengatur keduanya, setdan getke pribadi.

Ethan
sumber
28
"private" untuk membuatnya lokal ke file sumber, "internal" untuk membuatnya tetap lokal ke modul / proyek. private (set) hanya membuat set private. Pribadi membuat keduanya mengatur dan mendapatkan fungsi pribadi
user965972
3
Komentar pertama harus ditambahkan ke jawaban ini untuk membuatnya lebih lengkap.
Rob Norback
Saat ini (Swift 3.0.1) Level Kontrol Akses berubah: deklarasi "fileprivate" hanya dapat diakses oleh kode dalam file sumber yang sama dengan deklarasi ". Deklarasi "pribadi" hanya dapat diakses dengan kode di dalam lingkup terlampir deklarasi.
Jurasic
20

Ada dua cara dasar untuk melakukan apa yang Anda inginkan. Cara pertama adalah dengan memiliki properti pribadi dan dan properti publik yang dihitung yang mengembalikan properti itu:

public class Clock {

  private var _hours = 0

  public var hours: UInt {
    return _hours
  }

}

Tetapi ini dapat dicapai dengan cara yang berbeda dan lebih pendek, seperti yang dinyatakan dalam bagian "Kontrol Akses" dari buku "The Swift Programming Language" :

public class Clock {

    public private(set) var hours = 0

}

Sebagai catatan tambahan, Anda HARUS memberikan inisialisasi publik saat mendeklarasikan tipe publik. Bahkan jika Anda memberikan nilai default untuk semua properti, init()harus didefinisikan secara eksplisit untuk publik:

public class Clock {

    public private(set) var hours = 0

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

}

Ini juga dijelaskan di bagian buku yang sama, ketika berbicara tentang inisialisasi default.

Elitalon
sumber
5

Karena tidak ada kontrol akses (artinya Anda tidak dapat membuat kontrak akses yang berbeda tergantung pada siapa peneleponnya), inilah yang akan saya lakukan untuk saat ini:

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

Ini hanya menambah tingkat tipuan, seolah-olah, untuk mengarahkan akses ke konter; kelas lain dapat membuat Jam dan kemudian mengakses penghitungnya secara langsung. Tetapi idenya - yaitu, kontrak yang kami coba buat - adalah bahwa kelas lain harus menggunakan properti dan metode tingkat atas Clock saja. Kami tidak dapat menegakkan kontrak itu, tetapi sebenarnya sangat tidak mungkin untuk menerapkan di Objective-C juga.

matt
sumber
Sepertinya ini sudah berubah sejak saat itu, benar? developer.apple.com/swift/blog/?id=5
Dan Rosenstark
1
@Yakin, seluruh pertanyaan / jawaban ini ditulis jauh sebelum kontrol akses ditambahkan ke Swift. Dan Swift masih berevolusi, jadi jawaban akan terus usang.
matt
2

Sebenarnya kontrol akses (yang belum ada di Swift) tidak ditegakkan seperti yang Anda pikirkan di Objective C. Orang - orang dapat memodifikasi variabel readonly Anda secara langsung, jika mereka benar-benar menginginkannya. Mereka hanya tidak melakukannya dengan antarmuka publik kelas.

Anda dapat melakukan hal serupa di Swift (potong & rekatkan kode Anda, ditambah beberapa modifikasi, saya tidak mengujinya):

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

yang sama dengan apa yang Anda miliki di Objective C kecuali bahwa properti yang disimpan sebenarnya terlihat di antarmuka publik.

Di swift Anda juga bisa melakukan sesuatu yang lebih menarik, yang juga bisa Anda lakukan di Objective C, tetapi mungkin lebih cantik di swift (diedit di browser, saya tidak mengujinya):

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++
    }
}
File Analog
sumber
"Orang-orang dapat memodifikasi variabel hanya baca Anda secara langsung" - apa maksud Anda sebenarnya? Bagaimana?
user1244109
Saya merujuk ke Objective C. Di Objective C Anda memiliki akses dinamis ke segala sesuatu tentang kelas melalui API Runtime Objective C.
File Analog
Saya entah bagaimana mengerti Anda menyiratkan bahwa untuk cepat. Penasaran. Terima kasih
user1244109