Larutan
Compiler memperingatkan tentang hal ini karena suatu alasan. Sangat jarang peringatan ini diabaikan begitu saja, dan mudah ditangani. Begini caranya:
if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);
Atau lebih singkatnya (meskipun sulit dibaca & tanpa penjaga):
SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);
Penjelasan
Apa yang terjadi di sini adalah Anda meminta controller untuk pointer fungsi C untuk metode yang sesuai dengan controller. Semua NSObject
merespons methodForSelector:
, tetapi Anda juga dapat menggunakan class_getMethodImplementation
dalam runtime Objective-C (berguna jika Anda hanya memiliki referensi protokol, seperti id<SomeProto>
). Pointer fungsi ini disebut IMP
s, dan typedef
pointer fungsi ed sederhana ( id (*IMP)(id, SEL, ...)
) 1 . Ini mungkin dekat dengan tanda tangan metode aktual dari metode ini, tetapi tidak akan selalu sama persis.
Setelah Anda memiliki IMP
, Anda perlu melemparkannya ke pointer fungsi yang mencakup semua detail yang dibutuhkan ARC (termasuk dua argumen tersembunyi implisit self
dan _cmd
setiap panggilan metode Objective-C). Ini ditangani di baris ketiga ( (void *)
di sebelah kanan hanya memberitahu kompiler bahwa Anda tahu apa yang Anda lakukan dan tidak menghasilkan peringatan karena jenis pointer tidak cocok).
Akhirnya, Anda memanggil penunjuk fungsi 2 .
Contoh Kompleks
Saat pemilih mengambil argumen atau mengembalikan nilai, Anda harus mengubah sedikit hal:
SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
func(_controller, selector, someRect, someView) : CGRectZero;
Alasan untuk Peringatan
Alasan untuk peringatan ini adalah bahwa dengan ARC, runtime perlu tahu apa yang harus dilakukan dengan hasil dari metode yang Anda panggil. Hasilnya bisa apa saja: void
, int
, char
, NSString *
, id
, dll ARC biasanya mendapat informasi ini dari header dari jenis objek Anda bekerja dengan. 3
Sebenarnya hanya ada 4 hal yang akan dipertimbangkan ARC untuk nilai pengembalian: 4
- Jenis mengabaikan non-object (
void
, int
, dll)
- Pertahankan nilai objek, lalu lepaskan ketika tidak lagi digunakan (asumsi standar)
- Lepaskan nilai objek baru ketika tidak lagi digunakan (metode dalam
init
/ copy
keluarga atau dikaitkan dengan ns_returns_retained
)
- Jangan lakukan apa pun & asumsikan nilai objek yang dikembalikan akan valid dalam lingkup lokal (hingga kumpulan rilis terbanyak dikeringkan, dikaitkan dengan
ns_returns_autoreleased
)
Panggilan untuk methodForSelector:
mengasumsikan bahwa nilai balik dari metode yang dipanggil adalah objek, tetapi tidak mempertahankan / melepaskannya. Jadi Anda bisa membuat kebocoran jika objek Anda seharusnya dirilis seperti pada # 3 di atas (yaitu, metode yang Anda panggil mengembalikan objek baru).
Untuk penyeleksi yang Anda coba panggil balik itu void
atau yang bukan objek, Anda dapat mengaktifkan fitur kompiler untuk mengabaikan peringatan, tetapi itu mungkin berbahaya. Saya telah melihat dentang pergi melalui beberapa iterasi bagaimana ia menangani nilai-nilai kembali yang tidak ditugaskan ke variabel lokal. Tidak ada alasan bahwa dengan ARC diaktifkan, ARC tidak dapat mempertahankan dan melepaskan nilai objek yang dikembalikan methodForSelector:
meskipun Anda tidak ingin menggunakannya. Dari perspektif kompiler, itu adalah objek. Itu berarti bahwa jika metode yang Anda panggil,, someMethod
mengembalikan non objek (termasuk void
), Anda bisa berakhir dengan nilai penunjuk sampah dipertahankan / dirilis dan macet.
Argumen tambahan
Satu pertimbangan adalah bahwa ini adalah peringatan yang sama akan terjadi dengan performSelector:withObject:
dan Anda bisa mengalami masalah yang sama dengan tidak menyatakan bagaimana metode itu mengkonsumsi parameter. ARC memungkinkan untuk mendeklarasikan parameter yang dikonsumsi , dan jika metode tersebut menggunakan parameter tersebut, Anda mungkin pada akhirnya akan mengirim pesan ke zombie dan crash. Ada beberapa cara untuk mengatasi hal ini dengan casting bridged, tetapi sebenarnya akan lebih baik untuk hanya menggunakan IMP
metodologi fungsi pointer di atas. Karena parameter yang dikonsumsi jarang menjadi masalah, ini tidak mungkin muncul.
Selektor Statis
Menariknya, kompiler tidak akan mengeluh tentang pemilih yang dinyatakan secara statis:
[_controller performSelector:@selector(someMethod)];
Alasannya adalah karena kompiler sebenarnya dapat merekam semua informasi tentang pemilih dan objek selama kompilasi. Tidak perlu membuat asumsi tentang apa pun. (Saya memeriksa ini setahun yang lalu dengan melihat sumbernya, tetapi tidak memiliki referensi sekarang.)
Penekanan
Dalam mencoba memikirkan situasi di mana penindasan terhadap peringatan ini akan diperlukan dan desain kode yang baik, saya hampir kosong. Seseorang tolong bagikan jika mereka memiliki pengalaman di mana membungkam peringatan ini diperlukan (dan di atas tidak menangani hal-hal dengan benar).
Lebih
Dimungkinkan untuk membangun dan NSMethodInvocation
menangani ini juga, tetapi melakukannya membutuhkan lebih banyak pengetikan dan juga lebih lambat, jadi ada sedikit alasan untuk melakukannya.
Sejarah
Ketika performSelector:
keluarga metode pertama kali ditambahkan ke Objective-C, ARC tidak ada. Saat membuat ARC, Apple memutuskan bahwa peringatan harus dibuat untuk metode ini sebagai cara membimbing pengembang untuk menggunakan cara lain untuk secara eksplisit menentukan bagaimana memori harus ditangani ketika mengirim pesan sewenang-wenang melalui pemilih bernama. Di Objective-C, pengembang dapat melakukan ini dengan menggunakan gips gaya C pada pointer fungsi mentah.
Dengan diperkenalkannya Swift, Apple telah mendokumentasikan dalam performSelector:
keluarga metode sebagai "inheren tidak aman" dan mereka tidak tersedia untuk Swift.
Seiring waktu, kami telah melihat perkembangan ini:
- Versi awal Objective-C memungkinkan
performSelector:
(manajemen memori manual)
- Objective-C dengan ARC memperingatkan untuk penggunaan
performSelector:
- Swift tidak memiliki akses ke
performSelector:
dan mendokumentasikan metode ini sebagai "secara inheren tidak aman"
Akan tetapi, gagasan untuk mengirim pesan berdasarkan pemilih yang dinamai bukan fitur yang "pada dasarnya tidak aman". Ide ini telah berhasil digunakan sejak lama di Objective-C dan juga banyak bahasa pemrograman lainnya.
1 Semua metode Objective-C memiliki dua argumen tersembunyi, self
dan _cmd
yang secara implisit ditambahkan ketika Anda memanggil metode.
2 Memanggil NULL
fungsi tidak aman di C. Penjaga yang digunakan untuk memeriksa keberadaan pengontrol memastikan bahwa kami memiliki objek. Karena itu kami tahu kami akan mendapatkan IMP
dari methodForSelector:
(meskipun mungkin _objc_msgForward
, masuk ke sistem penerusan pesan). Pada dasarnya, dengan adanya penjaga, kita tahu bahwa kita memiliki fungsi untuk memanggil.
3 Sebenarnya, itu mungkin untuk mendapatkan info yang salah jika menyatakan objek sebagai id
Anda dan Anda tidak mengimpor semua header. Anda bisa berakhir dengan crash dalam kode yang menurut kompiler baik-baik saja. Ini sangat jarang, tetapi bisa terjadi. Biasanya Anda hanya akan mendapat peringatan bahwa ia tidak tahu mana dari dua tanda tangan metode yang dapat dipilih.
4 Lihat referensi ARC pada nilai retur dan nilai retur tanpa retret untuk detail lebih lanjut.