Saya biasanya melihat pertanyaan ini ditanyakan dengan cara lain, seperti Haruskah setiap ivar menjadi properti?(dan saya suka jawaban bbum untuk Q ini).
Saya menggunakan properti hampir secara eksklusif dalam kode saya. Namun, sering kali, saya bekerja dengan kontraktor yang telah lama mengembangkan iOS di dan merupakan programmer game tradisional. Dia menulis kode yang menyatakan hampir tidak ada properti apa pun dan bersandar pada ivars. Saya berasumsi dia melakukan ini karena 1.) dia terbiasa sejak properti tidak selalu ada sampai Objective C 2.0 (Oct '07) dan 2.) untuk keuntungan kinerja minimal tidak melalui pengambil / penyetel.
Sementara dia menulis kode yang tidak bocor, saya masih lebih suka dia menggunakan properti lebih dari ivars. Kami membicarakannya dan dia kira-kira tidak melihat alasan untuk menggunakan properti karena kami tidak menggunakan KVO dan dia berpengalaman menangani masalah memori.
Pertanyaan saya lebih lanjut ... Mengapa Anda ingin menggunakan periode ivar - berpengalaman atau tidak. Adakah perbedaan kinerja yang begitu besar sehingga menggunakan ivar dapat dibenarkan?
Juga sebagai titik klarifikasi, saya menimpa setter dan getter sesuai kebutuhan dan menggunakan ivar yang berkorelasi dengan properti di dalam pengambil / setter. Namun, di luar dari pengambil / penyetel atau init, saya selalu menggunakan self.myProperty
sintaks.
Edit 1
Saya menghargai semua tanggapan yang baik. Salah satu yang saya ingin alamat yang tampaknya salah adalah bahwa dengan ivar Anda mendapatkan enkapsulasi di mana dengan properti Anda tidak. Cukup tentukan properti dalam kelanjutan kelas. Ini akan menyembunyikan properti dari orang luar. Anda juga dapat mendeklarasikan properti yang hanya bisa dibaca di antarmuka dan mendefinisikannya kembali sebagai readwrite dalam implementasi seperti:
// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;
dan ada di kelanjutan kelas:
// readwrite within this file
@property (nonatomic, copy) NSString * name;
Untuk membuatnya benar-benar "pribadi" hanya menyatakannya dalam kelanjutan kelas.
Jawaban:
Enkapsulasi
Jika ivar bersifat pribadi, bagian lain dari program tidak bisa melakukannya dengan mudah. Dengan properti yang dideklarasikan, orang pintar dapat mengakses dan bermutasi dengan mudah melalui accessor.
Performa
Ya, ini dapat membuat perbedaan dalam beberapa kasus. Beberapa program memiliki kendala di mana mereka tidak dapat menggunakan pesan obrolan apa pun di bagian tertentu dari program (pikirkan waktu nyata). Dalam kasus lain, Anda mungkin ingin mengaksesnya secara langsung untuk kecepatan. Dalam kasus lain, itu karena pesan objc bertindak sebagai firewall optimasi. Akhirnya, ini dapat mengurangi operasi penghitungan referensi Anda dan meminimalkan penggunaan memori puncak (jika dilakukan dengan benar).
Jenis Nontrivial
Contoh: Jika Anda memiliki tipe C ++, akses langsung terkadang merupakan pendekatan yang lebih baik. Jenisnya mungkin tidak dapat disalin, atau mungkin tidak mudah untuk disalin.
Multithreading
Banyak dari ivar Anda tergantung pada kode. Anda harus memastikan integritas data Anda dalam konteks multithreaded. Dengan demikian, Anda dapat memilih akses langsung ke banyak anggota di bagian-bagian penting. Jika Anda tetap menggunakan accessor untuk data yang tergantung pada kode, kunci Anda biasanya harus reentrant dan Anda akan sering membuat lebih banyak akuisisi (kadang-kadang jauh lebih banyak).
Kebenaran Program
Karena subclass dapat mengesampingkan metode apa pun, pada akhirnya Anda mungkin melihat ada perbedaan semantik antara menulis ke antarmuka dibandingkan mengelola negara Anda dengan tepat. Akses langsung untuk kebenaran program sangat umum di negara bagian yang dibangun sebagian - di inisialisasi Anda dan
dealloc
, yang terbaik adalah menggunakan akses langsung. Anda juga dapat menemukan ini umum dalam implementasi accessor, constructor kenyamanancopy
,,mutableCopy
implementasi, dan pengarsipan / serialisasi.Ini juga lebih sering terjadi karena seseorang bergerak dari semuanya memiliki pola pikir accessor readwrite publik menjadi yang menyembunyikan rincian / data implementasinya dengan baik. Kadang-kadang Anda perlu melangkah dengan benar tentang efek samping yang mungkin ditimpa oleh subclass untuk melakukan hal yang benar.
Ukuran Biner
Mendeklarasikan semua readwrite secara default biasanya menghasilkan banyak metode accessor yang tidak pernah Anda butuhkan, ketika Anda mempertimbangkan eksekusi program Anda sejenak. Jadi itu akan menambah sedikit lemak untuk program Anda dan memuat waktu juga.
Meminimalkan Kompleksitas
Dalam beberapa kasus, itu sama sekali tidak perlu untuk menambahkan + type + mempertahankan semua perancah ekstra untuk variabel sederhana seperti bool pribadi yang ditulis dalam satu metode dan dibaca di yang lain.
Itu sama sekali tidak mengatakan menggunakan properti atau aksesor buruk - masing-masing memiliki manfaat dan batasan penting. Seperti banyak bahasa OO dan pendekatan desain, Anda juga harus mendukung pengakses dengan visibilitas yang sesuai di ObjC. Akan ada waktu yang Anda butuhkan untuk menyimpang. Untuk alasan itu, saya pikir sering kali terbaik untuk membatasi akses langsung ke implementasi yang mendeklarasikan ivar (mis. Mendeklarasikannya
@private
).Sunting kembali 1:
Sebagian besar dari kita telah hafal bagaimana memanggil accessor tersembunyi secara dinamis (selama kita tahu namanya ...). Sementara itu, sebagian besar dari kita belum hafal cara mengakses ivar dengan benar yang tidak terlihat (di luar KVC). Kelanjutan kelas membantu , tetapi itu memang memperkenalkan kerentanan.
Solusi ini sudah jelas:
Sekarang coba saja dengan ivar saja, dan tanpa KVC.
sumber
@private
, kompiler harus melarang akses anggota di luar kelas dan metode contoh - bukankah itu yang Anda lihat?Bagi saya biasanya kinerja. Mengakses ivar suatu objek sama cepatnya dengan mengakses anggota struct di C menggunakan pointer ke memori yang mengandung struct tersebut. Bahkan, objek Objective-C pada dasarnya adalah struct C yang terletak di memori yang dialokasikan secara dinamis. Ini biasanya secepat kode Anda bisa, kode perakitan bahkan tidak dioptimalkan dapat lebih cepat dari itu.
Mengakses ivar melalui pengambil / pengaturan melibatkan pemanggilan metode Objective-C, yang jauh lebih lambat (setidaknya 3-4 kali) daripada panggilan fungsi C "normal" dan bahkan panggilan fungsi C normal akan beberapa kali lebih lambat daripada mengakses anggota struct. Bergantung pada atribut properti Anda, implementasi penyetel / pengambil yang dihasilkan oleh kompiler dapat melibatkan pemanggilan fungsi C lain ke fungsi
objc_getProperty
/objc_setProperty
, karena ini harus keretain
/copy
/autorelease
objek sesuai kebutuhan dan selanjutnya melakukan pemintalan untuk properti atom jika diperlukan. Ini bisa dengan mudah menjadi sangat mahal dan saya tidak berbicara tentang menjadi 50% lebih lambat.Mari kita coba ini:
Keluaran:
Ini 4,28 kali lebih lambat dan ini adalah int primitif non-atom, cukup banyak kasus terbaik ; kebanyakan kasus lain bahkan lebih buruk (coba
NSString *
properti atom !). Jadi, jika Anda dapat hidup dengan fakta bahwa setiap akses ivar lebih lambat 4-5 kali dari yang seharusnya, menggunakan properti baik-baik saja (setidaknya ketika datang ke kinerja), namun, ada banyak situasi di mana penurunan kinerja seperti itu adalah benar-benar tidak dapat diterima.Perbarui 2015-10-20
Beberapa orang berpendapat, bahwa ini bukan masalah dunia nyata, kode di atas adalah murni sintetis dan Anda tidak akan pernah melihat bahwa dalam aplikasi nyata. Baiklah kalau begitu, mari kita coba sampel dunia nyata.
Kode berikut di bawah ini mendefinisikan
Account
objek. Akun memiliki properti yang menggambarkan nama (NSString *
), jenis kelamin (enum
), dan usia (unsigned
) pemiliknya, serta saldo (int64_t
). Objek akun memilikiinit
metode dancompare:
metode. Thecompare:
Metode didefinisikan sebagai: perintah Perempuan sebelum laki-laki, nama memesan abjad, perintah muda sebelum tua, perintah keseimbangan rendah ke tinggi.Sebenarnya ada dua kelas akun,
AccountA
danAccountB
. Jika Anda melihat implementasinya, Anda akan melihat bahwa mereka hampir seluruhnya identik, dengan satu pengecualian:compare:
Metode.AccountA
objek mengakses propertinya sendiri dengan metode (pengambil), sedangkanAccountB
objek mengakses properti mereka sendiri dengan ivar. Itulah satu-satunya perbedaan! Mereka berdua mengakses properti objek lain untuk dibandingkan dengan pengambil (mengaksesnya dengan ivar tidak akan aman! Bagaimana jika objek lainnya adalah subkelas dan telah menimpa pengambil?). Perhatikan juga bahwa mengakses properti Anda sendiri sebagai ivars tidak merusak enkapsulasi (ivars masih belum umum).Pengaturan pengujian sangat sederhana: Buat 1 akun acak Mio, tambahkan ke array dan urutkan array itu. Itu dia. Tentu saja, ada dua array, satu untuk
AccountA
objek dan satu untukAccountB
objek dan kedua array diisi dengan akun yang identik (sumber data yang sama). Kami menghitung berapa lama waktu yang dibutuhkan untuk mengurutkan array.Inilah output dari beberapa run yang saya lakukan kemarin:
Seperti yang Anda lihat, mengurutkan array
AccountB
objek adalah selalu signifikan lebih cepat daripada mengurutkan arrayAccountA
objek.Siapa pun yang mengklaim bahwa perbedaan runtime hingga 1,32 detik tidak membuat perbedaan sebaiknya tidak pernah melakukan pemrograman UI. Jika saya ingin mengubah urutan penyortiran tabel besar, misalnya, perbedaan waktu seperti ini memang membuat perbedaan besar bagi pengguna (perbedaan antara UI yang dapat diterima dan yang lamban).
Juga dalam hal ini kode sampel adalah satu-satunya pekerjaan nyata yang dilakukan di sini, tetapi seberapa sering kode Anda hanya berupa roda kecil dari jarum jam yang rumit? Dan jika setiap gigi memperlambat seluruh proses seperti ini, apa artinya bagi kecepatan seluruh jarum jam pada akhirnya? Terutama jika satu langkah kerja tergantung pada output yang lain, yang berarti semua inefisiensi akan meringkas. Kebanyakan inefisiensi bukanlah masalah mereka sendiri, itu adalah jumlah mereka yang menjadi masalah bagi keseluruhan proses. Dan masalah seperti itu bukanlah apa-apa yang akan dengan mudah ditampilkan oleh seorang profiler karena seorang profiler adalah tentang menemukan titik-titik kritis, tetapi tidak satupun dari ketidakefisienan ini adalah titik-titik panas itu sendiri. Waktu CPU rata-rata hanya menyebar di antara mereka, namun masing-masing dari mereka hanya memiliki sebagian kecil dari itu, tampaknya total buang waktu untuk mengoptimalkannya. Dan itu benar,
Dan bahkan jika Anda tidak berpikir dalam hal waktu CPU, karena Anda percaya membuang-buang waktu CPU benar-benar dapat diterima, setelah semua "ini gratis", lalu bagaimana dengan biaya server hosting yang disebabkan oleh konsumsi daya? Bagaimana dengan runtime baterai perangkat seluler? Jika Anda akan menulis aplikasi seluler yang sama dua kali (mis. Peramban web seluler sendiri), sekali versi di mana semua kelas mengakses properti mereka sendiri hanya dengan getter dan sekali di mana semua kelas mengaksesnya hanya dengan ivar, menggunakan yang pertama secara konstan pasti akan menguras baterai jauh lebih cepat daripada menggunakan yang kedua, meskipun mereka setara fungsional dan bagi pengguna yang kedua bahkan mungkin akan terasa sedikit lebih cepat.
Sekarang inilah kode untuk
main.m
file Anda (kode bergantung pada ARC yang diaktifkan dan pastikan untuk menggunakan pengoptimalan saat kompilasi untuk melihat efek penuh):sumber
unsigned int
yang tidak pernah disimpan / dirilis, apakah Anda menggunakan ARC atau tidak. Retain / release itu sendiri mahal, jadi perbedaannya akan lebih sedikit karena manajemen retain menambahkan overhead statis yang selalu ada, menggunakan setter / pengambil atau ivar secara langsung; namun Anda masih akan menyimpan overhead satu panggilan metode tambahan jika Anda mengakses ivar secara langsung. Bukan masalah besar dalam kebanyakan kasus, kecuali jika Anda melakukannya beberapa ribu kali per detik. Apple mengatakan menggunakan getter / setter secara default, kecuali jika Anda menggunakan metode init / dealloc atau melihat ada hambatan.copy
akan membuat salinan nilainya setiap kali Anda mengaksesnya. Pengambil dari properti adalah seperti pengambil dari / properti. Kode dasarnya . Hanya setter yang menyalin nilainya dan kira-kira akan terlihat seperti ini , sedangkan a / setter terlihat seperti ini:copy
strong
retain
return [[self->value retain] autorelease];
[self->value autorelease]; self->value = [newValue copy];
strong
retain
[self->value autorelease]; self->value = [newValue retain];
Alasan paling penting adalah konsep OOP menyembunyikan informasi : Jika Anda mengekspos segala sesuatu melalui properti dan dengan demikian memungkinkan objek eksternal untuk mengintip internal objek lain maka Anda akan menggunakan internal ini dan dengan demikian mempersulit mengubah implementasi.
Keuntungan "kinerja minimal" dapat dengan cepat meringkas dan kemudian menjadi masalah. Saya tahu dari pengalaman; Saya bekerja pada aplikasi yang benar-benar membawa iDevices ke batas mereka dan oleh karena itu kita perlu menghindari panggilan metode yang tidak perlu (tentu saja hanya jika memungkinkan). Untuk membantu dengan tujuan ini, kami juga menghindari sintaksis titik karena mempersulit untuk melihat jumlah pemanggilan metode pada pandangan pertama: misalnya, berapa banyak pemanggilan metode yang
self.image.size.width
dipicu oleh ekspresi ? Sebaliknya, Anda dapat langsung memberi tahu[[self image] size].width
.Juga, dengan penamaan ivar yang benar, KVO dimungkinkan tanpa properti (IIRC, saya bukan ahli KVO).
sumber
Semantik
@property
bisa menyatakan bahwa ivars tidak bisa:nonatomic
dancopy
.@property
tidak bisa:@protected
: publik di subclass, pribadi di luar.@package
: public on frameworks pada 64 bit, private outside. Sama seperti@public
pada 32 bit. Lihat Apple 64-bit Class dan Kontrol Akses Variabel Instans .id __strong *_objs
.Performa
Cerita pendek: ivars lebih cepat, tetapi itu tidak masalah untuk sebagian besar kegunaan.
nonatomic
properti tidak menggunakan kunci, tetapi langsung ivar lebih cepat karena melewatkan panggilan pengakses. Untuk detail, baca email berikut dari lists.apple.com.sumber
Properti vs variabel instance adalah trade-off, pada akhirnya pilihan turun ke aplikasi.
Enkapsulasi / Penyembunyian Informasi Ini adalah Good Thing (TM) dari perspektif desain, antarmuka yang sempit dan hubungan minimal adalah yang membuat perangkat lunak dapat dipelihara dan dimengerti. Cukup sulit di Obj-C untuk menyembunyikan apa pun, tetapi variabel instan yang dideklarasikan dalam implementasi sedekat yang Anda dapatkan.
Performa Sementara "optimasi prematur" adalah hal yang buruk (TM), menulis kode berkinerja buruk hanya karena Anda dapat setidaknya sama buruknya. Sulit untuk membantah panggilan metode yang lebih mahal daripada beban atau toko, dan dalam kode intensif komputasi biaya segera bertambah.
Dalam bahasa statis dengan properti, seperti C #, panggilan ke setter / getter sering dapat dioptimalkan jauh oleh kompiler. Namun Obj-C dinamis dan menghapus panggilan seperti itu jauh lebih sulit.
Abstraksi Argumen terhadap variabel instan dalam Obj-C secara tradisional adalah manajemen memori. Dengan MRC instance variabel memerlukan panggilan untuk mempertahankan / melepaskan / autorelease untuk disebarkan ke seluruh kode, properti (disintesis atau tidak) menyimpan kode MRC di satu tempat - prinsip abstraksi yang merupakan Good Thing (TM). Namun dengan GC atau ARC argumen ini hilang, jadi abstraksi untuk manajemen memori tidak lagi menjadi argumen terhadap variabel instan.
sumber
Properti mengekspos variabel Anda ke kelas lain. Jika Anda hanya perlu variabel yang relatif terhadap kelas yang Anda buat, gunakan variabel instan. Berikut adalah contoh kecil: kelas XML untuk parsing RSS dan sejenisnya melalui sekelompok metode delegasi dan semacamnya. Praktis untuk memiliki instance NSMutableString untuk menyimpan hasil dari setiap lintasan parse yang berbeda. Tidak ada alasan mengapa kelas luar perlu mengakses atau memanipulasi string itu. Jadi, Anda cukup mendeklarasikannya di header atau secara pribadi dan mengaksesnya di seluruh kelas. Mengatur properti untuk itu mungkin hanya berguna untuk memastikan tidak ada masalah memori, menggunakan self.mutableString untuk memanggil pengambil / penentu.
sumber
Kompatibilitas mundur adalah faktor bagi saya. Saya tidak dapat menggunakan fitur Objective-C 2.0 karena saya sedang mengembangkan perangkat lunak dan driver printer yang harus bekerja pada Mac OS X 10.3 sebagai bagian dari persyaratan. Saya tahu pertanyaan Anda sepertinya ditargetkan di iOS, tetapi saya pikir saya masih akan membagikan alasan saya untuk tidak menggunakan properti.
sumber