Menggunakan -performSelector: vs. hanya memanggil metode

116

Saya masih baru mengenal Objective-C dan saya bertanya-tanya apa perbedaan antara dua pernyataan berikut?

[object performSelector:@selector(doSomething)]; 

[object doSomething];
Penjudi
sumber

Jawaban:

191

Pada dasarnya performSelector memungkinkan Anda menentukan secara dinamis selektor mana yang akan dipanggil selektor pada objek tertentu. Dengan kata lain, pemilih tidak perlu ditentukan sebelum runtime.

Jadi meskipun ini setara:

[anObject aMethod]; 
[anObject performSelector:@selector(aMethod)]; 

Bentuk kedua memungkinkan Anda melakukan ini:

SEL aSelector = findTheAppropriateSelectorForTheCurrentSituation();
[anObject performSelector: aSelector];

sebelum Anda mengirim pesan.

ennuikiller
sumber
3
Perlu diperhatikan bahwa Anda sebenarnya akan menetapkan hasil findTheAppropriSelectorForTheCurrentSituation () ke aSelector, lalu panggil [anObject performSelector: aSelector]. @selector menghasilkan SEL.
Daniel Yankowsky
4
Menggunakan performSelector:adalah sesuatu yang mungkin hanya Anda lakukan jika Anda mengimplementasikan target-action di kelas Anda. Saudara kandung performSelectorInBackground:withObject:dan performSelectorOnMainThread:withObject:waitUntilDone:seringkali lebih berguna. Untuk menghasilkan thread latar belakang, dan untuk memanggil kembali hasil ke thread utama dari thread latar belakang tersebut.
PeyloW
2
performSelectorjuga berguna untuk menyembunyikan peringatan kompilasi. Jika Anda mengetahui bahwa metode tersebut ada (seperti setelah menggunakan respondsToSelector), Xcode akan berhenti mengatakan "mungkin tidak menanggapi your_selector". Hanya saja, jangan menggunakannya alih - alih mencari tahu penyebab sebenarnya dari peringatan tersebut. ;)
Marc
Saya membaca di beberapa utas lain di StackOverflow bahwa penggunaan performSelector adalah cerminan dari desain yang mengerikan, dan mendapat banyak jempol. Saya berharap saya dapat menemukannya lagi. Saya mencari di google, membatasi hasil ke stackoverflow, dan mendapatkan 18.000 hasil. Eww.
Logicsaurus Rex
"cerminan dari desain yang mengerikan" terlalu sederhana. Ini adalah apa yang kami miliki sebelum blok tersedia, dan tidak semua penggunaan buruk, dulu atau sekarang. Meskipun sekarang blok telah tersedia, itu mungkin pilihan yang lebih baik untuk kode baru kecuali Anda melakukan sesuatu yang sangat sederhana.
Ethan
16

Untuk contoh yang sangat mendasar dalam pertanyaan ini,

[object doSomething];
[object performSelector:@selector(doSomething)]; 

tidak ada perbedaan tentang apa yang akan terjadi. doSomething akan dieksekusi secara sinkron oleh objek. Hanya "doSomething" adalah metode yang sangat sederhana, yang tidak mengembalikan apa pun, dan tidak memerlukan parameter apa pun.

apakah itu sesuatu yang sedikit lebih rumit, seperti:

(void)doSomethingWithMyAge:(NSUInteger)age;

semuanya akan menjadi rumit, karena [object doSomethingWithMyAge: 42];

tidak bisa lagi dipanggil dengan varian apa pun dari "performSelector", karena semua varian dengan parameter hanya menerima parameter objek.

Pemilih di sini akan menjadi "doSomethingWithMyAge:" tetapi setiap upaya untuk melakukannya

[object performSelector:@selector(doSomethingWithMyAge:) withObject:42];  

tidak bisa dikompilasi. meneruskan NSNumber: @ (42) bukannya 42, juga tidak akan membantu, karena metode ini mengharapkan tipe C dasar - bukan objek.

Selain itu, ada varian performSelector hingga 2 parameter, tidak lebih. Sedangkan metode berkali-kali memiliki lebih banyak parameter.

Saya telah menemukan bahwa meskipun varian sinkron dari performSelector:

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

selalu mengembalikan objek, saya juga dapat mengembalikan BOOL atau NSUInteger sederhana, dan berhasil.

Salah satu dari dua kegunaan utama performSelector adalah untuk membuat nama metode yang ingin Anda jalankan secara dinamis, seperti yang dijelaskan di jawaban sebelumnya. Sebagai contoh

 SEL method = NSSelectorFromString([NSString stringWithFormat:@"doSomethingWithMy%@:", @"Age");
[object performSelector:method];

Kegunaan lainnya, adalah untuk mengirimkan pesan ke objek secara asinkron, yang akan dieksekusi nanti pada runloop saat ini. Untuk ini, ada beberapa varian performSelector lainnya.

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray *)modes;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;

(ya saya mengumpulkannya dari beberapa kategori kelas Foundation, seperti NSThread, NSRunLoop dan NSObject)

Tiap varian memiliki perilaku khususnya, tetapi semuanya memiliki kesamaan (setidaknya saat waitUntilDone disetel ke NO). Panggilan "performSelector" akan segera kembali, dan pesan ke objek hanya akan diletakkan di runloop saat ini setelah beberapa waktu.

Karena eksekusi tertunda - tentu saja tidak ada nilai pengembalian yang tersedia dari metode pemilih, oleh karena itu nilai pengembalian - (void) di semua varian asinkron ini.

Saya harap saya menutupi ini entah bagaimana ...

Motti Shneor
sumber
12

@ennuikiller tepat. Pada dasarnya, penyeleksi yang dibuat secara dinamis berguna ketika Anda tidak (dan biasanya tidak mungkin) mengetahui nama metode yang akan Anda panggil saat Anda mengompilasi kode.

Satu perbedaan utama adalah bahwa -performSelector:dan teman (termasuk varian multi-utas dan tertunda ) agak terbatas karena dirancang untuk digunakan dengan metode dengan parameter 0-2. Misalnya, memanggil -outlineView:toolTipForCell:rect:tableColumn:item:mouseLocation:dengan 6 parameter dan mengembalikan NSStringcukup berat, dan tidak didukung oleh metode yang disediakan.

Quinn Taylor
sumber
5
Untuk melakukan itu, Anda perlu menggunakan NSInvocationobjek.
Dave DeLong
6
Perbedaan lainnya: performSelector:dan teman-teman semua mengambil argumen objek, artinya Anda tidak dapat menggunakannya untuk memanggil (misalnya) setAlphaValue:, karena argumennya adalah pelampung.
Chuck
4

Penyeleksi agak mirip dengan pointer fungsi dalam bahasa lain. Anda menggunakannya saat tidak tahu pada waktu kompilasi metode mana yang ingin Anda panggil saat runtime. Juga, seperti pointer fungsi, mereka hanya merangkum bagian kata kerja dari pemanggilan. Jika metode tersebut memiliki parameter, Anda harus meneruskannya juga.

An NSInvocationmemiliki tujuan yang sama, kecuali bahwa ia mengikat lebih banyak informasi. Tidak hanya menyertakan bagian kata kerja, ini juga menyertakan objek target dan parameter. Ini berguna saat Anda ingin memanggil metode pada objek tertentu dengan parameter tertentu, bukan untuk saat ini tetapi di masa mendatang. Anda dapat membuat yang sesuai NSInvocationdan memecatnya nanti.

Daniel Yankowsky
sumber
5
Selektor sama sekali tidak seperti penunjuk fungsi karena penunjuk fungsi adalah sesuatu yang dapat Anda panggil dengan argumen dan selektor dapat digunakan untuk memanggil metode tertentu pada objek apa pun yang mengimplementasikannya; selektor tidak memiliki konteks lengkap pemanggilan seperti penunjuk fungsi.
bbum
1
Selektor tidak sama dengan penunjuk fungsi, tapi menurut saya keduanya serupa. Mereka mewakili kata kerja. Pointer fungsi C juga mewakili kata kerja. Tidak ada yang berguna tanpa konteks tambahan. Penyeleksi membutuhkan objek dan parameter; pointer fungsi membutuhkan parameter (yang mungkin termasuk objek untuk dioperasikan). Maksud saya adalah untuk menyoroti bagaimana mereka berbeda dari objek NSInvocation, yang berisi semua konteks yang diperlukan. Mungkin perbandingan saya membingungkan, dalam hal ini saya minta maaf.
Daniel Yankowsky
1
Pemilih bukanlah penunjuk fungsi. Bahkan tidak dekat. Mereka sebenarnya adalah string C sederhana, yang mengandung "nama" dari sebuah metode (sebagai lawan dari 'fungsi'). Mereka bahkan bukan tanda tangan metode, karena mereka tidak menyematkan tipe parameter. Sebuah objek dapat memiliki lebih dari satu metode untuk selektor yang sama (tipe param berbeda, atau tipe kembalian berbeda).
Motti Shneor
-7

Ada perbedaan halus lainnya di antara keduanya.

    [object doSomething]; // is executed right away

    [object performSelector:@selector(doSomething)]; // gets executed at the next runloop

Berikut adalah kutipan dari Dokumentasi Apple

"performSelector: withObject: afterDelay: Menjalankan selektor yang ditentukan pada thread saat ini selama siklus run loop berikutnya dan setelah periode penundaan opsional. Karena metode ini menunggu hingga siklus run loop berikutnya untuk menjalankan pemilih, metode ini menyediakan penundaan mini otomatis dari kode yang saat ini sedang dijalankan. Beberapa antrian pemilih dilakukan satu demi satu dalam urutan antrian mereka. "

avi
sumber
1
Jawaban Anda salah secara faktual. Dokumentasi yang Anda kutip adalah tentang performSelector:withObject:afterDelay:, tetapi pertanyaan dan cuplikan Anda gunakan performSelector:, yang merupakan metode yang sama sekali berbeda. Dari dokumen untuk itu: <quote> performSelector:Metodenya sama dengan mengirim aSelectorpesan langsung ke penerima. </quote>
jscs
3
terima kasih Josh atas klarifikasinya. Anda benar; Saya pikir performSelector/performSelector:withObject/performSelector:withObject:afterDelaysemua berperilaku sama yang merupakan kesalahan.
avi