Properti Hanya Baca di Objective-C?

93

Saya telah mendeklarasikan properti hanya-baca di antarmuka saya seperti ini:

 @property (readonly, nonatomic, copy) NSString* eventDomain;

Mungkin saya salah paham tentang properti, tetapi saya pikir ketika Anda mendeklarasikannya sebagai readonly, Anda dapat menggunakan penyetel yang dihasilkan di dalam file implementasi ( .m), tetapi entitas eksternal tidak dapat mengubah nilainya. Pertanyaan SO ini mengatakan bahwa itulah yang seharusnya terjadi. Itulah perilaku yang saya kejar. Namun, ketika mencoba menggunakan penyetel standar atau sintaks titik untuk menyetel eventDomaindi dalam metode init saya, ini memberi saya unrecognized selector sent to instance.kesalahan. Tentu saja aku akan @synthesizemendapatkan propertinya. Mencoba menggunakannya seperti ini:

 // inside one of my init methods
 [self setEventDomain:@"someString"]; // unrecognized selector sent to instance error

Jadi, apakah saya salah memahami readonlypernyataan tentang properti? Atau ada hal lain yang sedang terjadi?

Alex
sumber

Jawaban:

116

Anda perlu memberi tahu kompilator bahwa Anda juga menginginkan penyetel. Cara yang umum adalah dengan meletakkannya di ekstensi kelas di file .m:

@interface YourClass ()

@property (nonatomic, copy) NSString* eventDomain;

@end
Eiko
sumber
22
Ini bukan kategori. Ini adalah perpanjangan kelas (seperti yang dikatakan Eiko). Mereka sangat berbeda, meskipun digunakan untuk tujuan yang serupa. Anda tidak bisa, misalnya, melakukan hal di atas dalam kategori benar.
bbum
2
Saya perhatikan bahwa kelas yang mewarisi dari kelas yang memiliki properti ekstensi kelas, tidak akan melihatnya. yaitu warisan hanya melihat antarmuka eksternal. Konfirmasi?
Yogev Shelly
5
Itu bukan album yang adil. Ini mungkin berbeda, tetapi ini dikenal sebagai "kategori Anonim"
MaxGabriel
3
Apakah ini selain deklarasi awal operasi di antarmuka publik? Artinya, apakah deklarasi ekstensi kelas ini menggantikan deklarasi awal di .h? Kalau tidak, saya tidak melihat bagaimana ini akan mengekspos setter publik. Terima kasih
Madbreaks
4
@Madbreaks Ini tambahan, tapi ada di file .m. Jadi kompilator tahu untuk membuatnya menjadi readwrite untuk kelas itu, tetapi penggunaan eksternal masih terbatas hanya untuk membaca seperti yang diharapkan.
Eiko
38

Eiko dan yang lainnya memberikan jawaban yang benar.

Berikut cara yang lebih sederhana: Akses langsung variabel anggota pribadi.

Contoh

Di file header .h:

@property (strong, nonatomic, readonly) NSString* foo;

Dalam implementasi file .m:

// inside one of my init methods
self->_foo = @"someString"; // Notice the underscore prefix of var name.

Itu saja, itu saja yang Anda butuhkan. Tidak repot, tidak repot.

Detail

Mulai Xcode 4.4 dan LLVM Compiler 4.0 ( Fitur Baru di Xcode 4.4 ), Anda tidak perlu mengacaukan tugas-tugas yang dibahas dalam jawaban lain:

  • Kata synthesizekunci
  • Mendeklarasikan variabel
  • Mendeklarasikan ulang properti dalam implementasi file .m.

Setelah mendeklarasikan properti foo, Anda dapat mengasumsikan Xcode telah menambahkan variabel anggota pribadi bernama dengan awalan garis bawah: _foo.

Jika properti dideklarasikan readwrite, Xcode menghasilkan metode pengambil bernama foodan penyetel bernama setFoo. Metode ini secara implisit dipanggil ketika Anda menggunakan notasi titik (Object.myMethod saya). Jika properti dideklarasikan readonly, tidak ada penyetel yang dibuat. Itu berarti variabel pendukung, dinamai dengan garis bawah, itu sendiri tidak hanya dapat dibaca. The readonlyberarti hanya bahwa ada metode setter disintesis, dan karena itu menggunakan notasi dot untuk menetapkan nilai gagal dengan kesalahan kompilator. Notasi titik gagal karena kompilator menghentikan Anda dari memanggil metode (penyetel) yang tidak ada.

Cara termudah untuk menyiasatinya adalah dengan langsung mengakses variabel anggota, dinamai dengan garis bawah. Anda dapat melakukannya bahkan tanpa mendeklarasikan variabel bernama garis bawah! Xcode memasukkan deklarasi itu sebagai bagian dari proses build / compile, jadi kode yang Anda kompilasi memang akan memiliki deklarasi variabel. Tapi Anda tidak pernah melihat deklarasi itu di file kode sumber asli Anda. Bukan sihir, hanya gula sintaksis .

Menggunakan self->adalah cara untuk mengakses variabel anggota dari objek / instance. Anda mungkin bisa menghilangkannya, dan cukup gunakan nama var. Tapi saya lebih suka menggunakan panah + diri karena itu membuat kode saya terdokumentasi sendiri. Jika Anda melihat self->_fooAnda tahu tanpa ambiguitas, itu _fooadalah variabel anggota pada contoh ini.


By the way, diskusi pro dan kontra dari accesor properti terhadap akses Ivar langsung adalah persis jenis pengobatan bijaksana Anda akan membaca di Dr Matt Neuberg 's Programming iOS buku. Saya merasa sangat membantu untuk membaca dan membaca ulang.

Basil Bourque
sumber
1
Mungkin Anda harus menambahkan bahwa seseorang tidak boleh mengakses variabel anggota secara langsung (dari tanpa kelasnya)! Melakukannya merupakan pelanggaran OOP (penyembunyian informasi).
HAS
2
@HAS Benar, skenario di sini adalah mengatur variabel anggota secara pribadi tidak terlihat dari luar kelas ini. Itulah inti dari pertanyaan pembuat poster asli: Mendeklarasikan properti readonlysehingga tidak ada kelas lain yang dapat menyetelnya.
Basil Bourque
Saya melihat posting ini tentang cara membuat properti hanya-baca. Ini tidak berhasil untuk saya. Itu memungkinkan saya untuk menetapkan nilai di init, tetapi saya juga dapat mengubah _variable nanti dengan sebuah tugas. itu tidak memunculkan kesalahan atau peringatan kompilator.
netskink
@BasilBourque Terima kasih banyak atas hasil edit yang membantu pertanyaan "ketekunan" itu!
GhostCat
36

Cara lain yang saya temukan untuk bekerja dengan properti hanya-baca adalah dengan menggunakan @synthesize untuk menentukan penyimpanan dukungan. Sebagai contoh

@interface MyClass

@property (readonly) int whatever;

@end

Kemudian dalam implementasinya

@implementation MyClass

@synthesize whatever = m_whatever;

@end

Metode Anda kemudian dapat disetel m_whatever, karena ini adalah variabel anggota.


Hal menarik lainnya yang saya sadari dalam beberapa hari terakhir adalah Anda dapat membuat properti hanya-baca yang dapat ditulis oleh subkelas seperti:

(di file header)

@interface MyClass
{
    @protected
    int m_propertyBackingStore;
}

@property (readonly) int myProperty;

@end

Kemudian dalam implementasinya

@synthesize myProperty = m_propertyBackingStore;

Ini akan menggunakan deklarasi di file header, sehingga subclass dapat memperbarui nilai properti, sambil mempertahankan keterbacaannya.

Sedikit disayangkan dalam hal penyembunyian dan enkapsulasi data.

ya Tidak
sumber
1
Cara pertama Anda menjelaskan lebih mudah ditangani daripada jawaban yang diterima. Hanya "@sintesis foo1, foo2, foo3;" di .m, dan semuanya baik-baik saja.
sudo
20

Lihat Menyesuaikan Kelas yang Ada di iOS Docs.

readonly Menunjukkan bahwa properti read-only. Jika Anda menetapkan hanya-baca, hanya metode pengambil yang diperlukan dalam @implementation. Jika Anda menggunakan @synthesize di blok implementasi, hanya metode pengambil yang disintesis. Selain itu, jika Anda mencoba untuk menetapkan nilai menggunakan sintaks titik, Anda mendapatkan kesalahan kompilator.

Properti hanya-baca hanya memiliki metode pengambil. Anda masih dapat menyetel ivar pendukung langsung di dalam kelas properti atau menggunakan pengkodean nilai kunci.

Yunus
sumber
9

Anda salah paham dengan pertanyaan lain. Dalam pertanyaan itu ada ekstensi kelas, yang dinyatakan sebagai berikut:

@interface MYShapeEditorDocument ()
@property (readwrite, copy) NSArray *shapesInOrderBackToFront;
@end

Itulah yang menghasilkan setter yang hanya terlihat dalam implementasi kelas. Jadi seperti yang dikatakan Eiko, Anda perlu mendeklarasikan ekstensi kelas dan mengganti deklarasi properti untuk memberi tahu kompilator untuk menghasilkan penyetel hanya di dalam kelas.

BoltClock
sumber
5

Solusi terpendek adalah:

MyClass.h

@interface MyClass {

  int myProperty;

}

@property (readonly) int myProperty;

@end

MyClass.h

@implementation MyClass

@synthesize myProperty;

@end
pengguna2159978
sumber
2

Jika properti didefinisikan sebagai readonly, itu berarti bahwa secara efektif tidak akan ada penyetel yang dapat digunakan baik secara internal ke kelas atau secara eksternal dari kelas lain. (yaitu: Anda hanya akan memiliki "getter" jika itu masuk akal.)

Dari bunyinya, Anda menginginkan properti baca / tulis normal yang ditandai sebagai privat, yang dapat Anda capai dengan menyetel variabel kelas sebagai privat di file antarmuka Anda seperti:

@private
    NSString* eventDomain;
}
John Parker
sumber