Saya awalnya seorang programmer Java yang sekarang bekerja dengan Objective-C. Saya ingin membuat kelas abstrak, tetapi itu tampaknya tidak mungkin di Objective-C. Apakah ini mungkin?
Jika tidak, seberapa dekat dengan kelas abstrak yang bisa saya dapatkan di Objective-C?
objective-c
abstract-class
Jonathan Arbogast
sumber
sumber
Jawaban:
Biasanya, kelas Objective-C adalah abstrak oleh konvensi saja — jika penulis mendokumentasikan kelas sebagai abstrak, hanya saja jangan menggunakannya tanpa mensubklasifikasikan. Namun, tidak ada penegakan waktu kompilasi yang mencegah instantiasi kelas abstrak. Bahkan, tidak ada yang menghentikan pengguna dari memberikan implementasi metode abstrak melalui kategori (yaitu saat runtime). Anda bisa memaksa pengguna untuk setidaknya menimpa metode tertentu dengan meningkatkan pengecualian dalam implementasi metode tersebut di kelas abstrak Anda:
Jika metode Anda mengembalikan nilai, itu sedikit lebih mudah digunakan
karena Anda tidak perlu menambahkan pernyataan kembali dari metode.
Jika kelas abstrak benar-benar sebuah antarmuka (yaitu tidak memiliki implementasi metode konkret), menggunakan protokol Objective-C adalah pilihan yang lebih tepat.
sumber
@protocol
definisi, tetapi Anda tidak dapat menentukan metode di sana.+ (instancetype)exceptionForCallingAbstractMethod:(SEL)selector
ini berfungsi dengan sangat baik.doesNotRecognizeSelector
.Tidak, tidak ada cara untuk membuat kelas abstrak di Objective-C.
Anda bisa mengejek kelas abstrak - dengan membuat metode / penyeleksi memanggil doesNotRecognizeSelector: dan karenanya memunculkan pengecualian membuat kelas tidak dapat digunakan.
Sebagai contoh:
Anda juga dapat melakukan ini untuk init.
sumber
NSObject
referensi menyarankan menggunakan ini di mana Anda TIDAK ingin mewarisi metode, bukan untuk menegakkan menimpa metode. Meskipun itu mungkin hal yang sama, mungkin :)Hanya membaca jawaban @Barry Wark di atas (dan memperbarui untuk iOS 4.3) dan meninggalkan ini untuk referensi saya sendiri:
maka dalam metode Anda, Anda dapat menggunakan ini
Catatan: Tidak yakin apakah membuat makro terlihat seperti fungsi C adalah ide yang baik atau tidak, tapi saya akan menyimpannya sampai bersekolah sebaliknya. Saya pikir itu lebih tepat untuk digunakan
NSInvalidArgumentException
(daripadaNSInternalInconsistencyException
) karena itulah yang melempar sistem runtime sebagai tanggapandoesNotRecognizeSelector
dipanggil (lihatNSObject
dokumen).sumber
universe.thing
tetapi diperluas[Universe universe].thing
. Sangat menyenangkan, menghemat ribuan surat kode ...#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
.__PRETTY_FUNCTION__
semua tempat, melalui makro DLog (...), seperti yang disarankan di sini: stackoverflow.com/a/969291/38557 .#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'
dalam kasus saya mengusulkan Anda juga mendapatkan diHomeViewController - method not implemented
mana HomeViewController diwarisi dari Base - ini akan memberikan informasi lebih lanjutSolusi yang saya temukan adalah:
Dengan cara ini kompiler akan memberi Anda peringatan untuk metode apa pun dalam protokol yang tidak diterapkan oleh kelas anak Anda.
Ini tidak sesingkat di Jawa, tetapi Anda mendapatkan peringatan kompiler yang diinginkan.
sumber
NSObject
). Jika Anda kemudian meletakkan protokol dan deklarasi kelas dasar dalam file header yang sama itu hampir tidak bisa dibedakan dari kelas abstrak.Dari milis Omni Group :
Objective-C tidak memiliki konstruksi kompiler abstrak seperti Java saat ini.
Jadi yang Anda lakukan adalah mendefinisikan kelas abstrak sebagai kelas normal lainnya dan menerapkan metode stubs untuk metode abstrak yang kosong atau melaporkan non-dukungan untuk pemilih. Sebagai contoh...
Saya juga melakukan hal berikut untuk mencegah inisialisasi kelas abstrak melalui penginisialisasi default.
sumber
Alih-alih mencoba membuat kelas dasar abstrak, pertimbangkan untuk menggunakan protokol (mirip dengan antarmuka Java). Ini memungkinkan Anda untuk mendefinisikan serangkaian metode, dan kemudian menerima semua objek yang sesuai dengan protokol dan mengimplementasikan metode. Sebagai contoh, saya dapat mendefinisikan protokol Operasi, dan kemudian memiliki fungsi seperti ini:
Di mana op dapat berupa objek yang mengimplementasikan protokol Operasi.
Jika Anda membutuhkan kelas dasar abstrak untuk melakukan lebih dari sekadar mendefinisikan metode, Anda bisa membuat kelas Objective-C reguler dan mencegahnya agar tidak dipakai. Timpa saja fungsi init - (id) dan buat kembali nil atau menegaskan (false). Ini bukan solusi yang sangat bersih, tetapi karena Objective-C sepenuhnya dinamis, benar-benar tidak ada yang setara langsung dengan kelas dasar abstrak.
sumber
Utas ini agak lama, dan sebagian besar yang ingin saya bagikan sudah ada di sini.
Namun, metode favorit saya tidak disebutkan, dan AFAIK tidak ada dukungan asli di Dentang saat ini, jadi di sini saya pergi ...
Pertama, dan yang terpenting (seperti yang telah ditunjukkan orang lain) kelas abstrak adalah sesuatu yang sangat tidak biasa di Objective-C - kita biasanya menggunakan komposisi (kadang-kadang melalui delegasi) sebagai gantinya. Ini mungkin alasan mengapa fitur seperti itu belum ada dalam bahasa / kompiler - terlepas dari
@dynamic
properti, yang IIRC telah ditambahkan dalam ObjC 2.0 yang menyertai pengenalan CoreData.Tetapi mengingat (setelah menilai situasi Anda dengan cermat!) Anda sampai pada kesimpulan bahwa delegasi (atau komposisi secara umum) tidak cocok untuk menyelesaikan masalah Anda, inilah cara saya melakukannya:
[self doesNotRecognizeSelector:_cmd];
...__builtin_unreachable();
membungkam peringatan yang akan Anda dapatkan untuk metode non-void, memberi tahu Anda "kontrol mencapai fungsi non-void tanpa pengembalian".-[NSObject doesNotRecognizeSelector:]
menggunakan__attribute__((__noreturn__))
dalam kategori tanpa implementasi agar tidak menggantikan implementasi asli dari metode itu, dan termasuk header untuk kategori itu dalam PCH proyek Anda.Saya pribadi lebih suka versi makro karena memungkinkan saya untuk mengurangi boilerplate sebanyak mungkin.
Ini dia:
Seperti yang Anda lihat, makro menyediakan implementasi penuh dari metode abstrak, mengurangi jumlah yang diperlukan pelat ke minimum absolut.
Opsi yang lebih baik lagi adalah melobi tim Dentang untuk menyediakan atribut kompiler untuk kasus ini, melalui permintaan fitur. (Lebih baik, karena ini juga akan memungkinkan diagnostik waktu kompilasi untuk skenario di mana Anda mensubclass misalnya NSIncrementalStore.)
Mengapa Saya Memilih Metode Ini
__builtin_unreachable()
mungkin mengejutkan orang, tetapi juga cukup mudah dimengerti.)Poin terakhir itu perlu penjelasan, saya kira:
Beberapa (sebagian besar?) Orang melepaskan pernyataan dalam versi rilis. (Saya tidak setuju dengan kebiasaan itu, tapi itu cerita lain ...) Gagal menerapkan metode yang diperlukan - namun - buruk , mengerikan , salah , dan pada dasarnya akhir dari jagat raya untuk program Anda. Program Anda tidak dapat bekerja dengan benar dalam hal ini karena tidak terdefinisi, dan perilaku tidak terdefinisi adalah hal terburuk yang pernah ada. Oleh karena itu, dapat menghapus diagnostik tersebut tanpa menghasilkan diagnostik baru akan sepenuhnya tidak dapat diterima.
Sudah cukup buruk bahwa Anda tidak dapat memperoleh diagnosa waktu kompilasi yang tepat untuk kesalahan programmer seperti itu, dan harus menggunakan penemuan pada saat run-time untuk hal ini, tetapi jika Anda dapat memplesternya dalam rilis build, mengapa mencoba memiliki kelas abstrak di tempat pertama?
sumber
__builtin_unreachable();
permata membuat ini bekerja dengan sempurna. Makro membuatnya mendokumentasikan diri sendiri dan perilaku cocok dengan apa yang terjadi jika Anda memanggil metode yang hilang pada objek.Menggunakan
@property
dan@dynamic
juga bisa bekerja. Jika Anda mendeklarasikan properti dinamis dan tidak memberikan implementasi metode pencocokan, semuanya akan tetap dikompilasi tanpa peringatan, dan Anda akan mendapatkanunrecognized selector
kesalahan saat runtime jika Anda mencoba mengaksesnya. Ini pada dasarnya hal yang sama dengan menelepon[self doesNotRecognizeSelector:_cmd]
, tetapi dengan mengetik jauh lebih sedikit.sumber
Dalam Xcode (menggunakan dentang dll) saya suka menggunakan
__attribute__((unavailable(...)))
untuk menandai kelas abstrak sehingga Anda mendapatkan kesalahan / peringatan jika Anda mencoba dan menggunakannya.Ini memberikan beberapa perlindungan terhadap tidak sengaja menggunakan metode ini.
Contoh
Di
@interface
tag kelas dasar metode "abstrak":Mengambil langkah ini lebih jauh, saya membuat makro:
Ini memungkinkan Anda melakukan ini:
Seperti yang saya katakan, ini bukan perlindungan kompiler yang sebenarnya, tetapi ini sama baiknya dengan yang Anda dapatkan dalam bahasa yang tidak mendukung metode abstrak.
sumber
-init
metode dalam subkelas Anda.Jawaban atas pertanyaan tersebar di komentar di bawah jawaban yang sudah diberikan. Jadi, saya hanya merangkum dan menyederhanakan di sini.
Opsi1: Protokol
Jika Anda ingin membuat kelas abstrak tanpa implementasi, gunakan 'Protokol'. Kelas-kelas yang mewarisi protokol diwajibkan untuk mengimplementasikan metode-metode dalam protokol.
Opsi2: Pola Metode Templat
Jika Anda ingin membuat kelas abstrak dengan implementasi parsial seperti "Pola Metode Templat" maka ini solusinya. Objective-C - Pola metode templat?
sumber
Alternatif lain
Cukup periksa kelas di kelas Abstrak dan Tegas atau Pengecualian, apa pun yang Anda suka.
Ini menghilangkan keharusan untuk menimpa
init
sumber
(lebih dari saran terkait)
Saya ingin memiliki cara untuk membiarkan programmer tahu "jangan menelepon dari anak" dan untuk menimpanya sepenuhnya (dalam kasus saya masih menawarkan beberapa fungsi default atas nama orang tua ketika tidak diperpanjang):
Keuntungannya adalah bahwa programmer akan MELIHAT "menimpa" dalam deklarasi dan akan tahu mereka tidak boleh menelepon
[super ..]
.Memang, itu jelek harus mendefinisikan jenis kembali individu untuk ini, tetapi berfungsi sebagai petunjuk visual yang cukup baik dan Anda tidak dapat dengan mudah menggunakan bagian "override_" dalam definisi subclass.
Tentu saja sebuah kelas masih dapat memiliki implementasi standar ketika ekstensi bersifat opsional. Tapi seperti jawaban lain katakan, mengimplementasikan pengecualian run-time saat yang tepat, seperti untuk kelas abstrak (virtual).
Akan menyenangkan untuk memiliki built-in petunjuk kompiler seperti ini, bahkan petunjuk untuk saat yang terbaik untuk melakukan pra / posting panggilan implementasi super, daripada harus menggali melalui komentar / dokumentasi atau ... menganggap.
sumber
Jika Anda terbiasa dengan kompiler yang menangkap pelanggaran instantiasi abstrak dalam bahasa lain, maka perilaku Objective-C mengecewakan.
Sebagai bahasa pengikat yang terlambat, jelaslah bahwa Objective-C tidak dapat membuat keputusan statis tentang apakah suatu kelas benar-benar abstrak atau tidak (Anda mungkin menambahkan fungsi pada saat runtime ...), tetapi untuk kasus-kasus penggunaan umum ini sepertinya kekurangan. Saya lebih suka kompilasi flat-out mencegah instance kelas abstrak daripada melemparkan kesalahan saat runtime.
Berikut adalah pola yang kami gunakan untuk mendapatkan pemeriksaan statis jenis ini menggunakan beberapa teknik untuk menyembunyikan inisialisasi:
sumber
Mungkin situasi seperti ini seharusnya hanya terjadi pada waktu pengembangan, jadi ini mungkin berhasil:
sumber
Anda dapat menggunakan metode yang diusulkan oleh @Yar (dengan beberapa modifikasi):
Di sini Anda akan mendapatkan pesan seperti:
Atau pernyataan:
Dalam hal ini Anda akan mendapatkan:
Anda juga dapat menggunakan protokol dan solusi lain - tetapi ini adalah salah satu yang paling sederhana.
sumber
Kakao tidak menyediakan apa pun yang disebut abstrak. Kita dapat membuat abstrak kelas yang diperiksa hanya saat runtime, dan pada waktu kompilasi ini tidak dicentang.
sumber
Saya biasanya hanya menonaktifkan metode init di kelas yang ingin saya abstraksi:
Ini akan menghasilkan kesalahan pada waktu kompilasi setiap kali Anda memanggil init pada kelas itu. Saya kemudian menggunakan metode kelas untuk yang lainnya.
Objective-C tidak memiliki cara bawaan untuk mendeklarasikan kelas abstrak.
sumber
Mengubah sedikit apa yang disarankan @redfood dengan menerapkan komentar @ dotToString, Anda sebenarnya memiliki solusi yang diadopsi oleh IGListKit Instagram .
AbstractClass
harus diinput ke atau output dengan beberapa metode, ketik itu sebagaiAbstractClass<Protocol>
gantinya.Karena
AbstractClass
tidak diterapkanProtocol
, satu-satunya cara untuk memilikiAbstractClass<Protocol>
instance adalah dengan subclassing. KarenaAbstractClass
sendirian tidak dapat digunakan di mana pun dalam proyek, itu menjadi abstrak.Tentu saja, ini tidak mencegah pengembang yang tidak disarankan untuk menambahkan metode baru yang hanya merujuk
AbstractClass
, yang pada akhirnya memungkinkan turunan dari kelas abstrak (tidak lagi).Contoh dunia nyata: IGListKit memiliki kelas dasar
IGListSectionController
yang tidak mengimplementasikan protokolIGListSectionType
, namun setiap metode yang membutuhkan turunan dari kelas itu, sebenarnya meminta jenisnyaIGListSectionController<IGListSectionType>
. Oleh karena itu tidak ada cara untuk menggunakan objek bertipeIGListSectionController
untuk apa pun yang berguna dalam kerangka mereka.sumber
Faktanya, Objective-C tidak memiliki kelas abstrak, tetapi Anda dapat menggunakan Protokol untuk mencapai efek yang sama. Berikut ini contohnya:
CustomProtocol.h
TestProtocol.h
TestProtocol.m
sumber
Contoh sederhana membuat kelas abstrak
sumber
Tidak bisakah Anda membuat delegasi saja?
Delegasi adalah seperti kelas dasar abstrak dalam arti bahwa Anda mengatakan fungsi apa yang perlu didefinisikan, tetapi Anda tidak benar-benar mendefinisikannya.
Kemudian setiap kali Anda menerapkan delegasi Anda (yaitu kelas abstrak) Anda akan diperingatkan oleh kompiler tentang fungsi opsional dan wajib yang Anda butuhkan untuk mendefinisikan perilaku.
Ini kedengarannya seperti kelas dasar abstrak bagi saya.
sumber