Objective-C: perbedaan antara id dan void *

Jawaban:

240

void * berarti "referensi ke sejumlah memori acak dengan isi yang tidak diketik / tidak dikenal"

id berarti "referensi ke beberapa objek Objective-C acak kelas tidak diketahui"

Ada perbedaan semantik tambahan:

  • Di bawah Hanya GC atau mode yang Didukung GC, kompiler akan memancarkan hambatan penulisan untuk referensi tipe id, tetapi tidak untuk tipe void *. Ketika mendeklarasikan struktur, ini bisa menjadi perbedaan kritis. Mendeklarasikan iVars seperti void *_superPrivateDoNotTouch;akan menyebabkan menuai prematur objek jika _superPrivateDoNotTouchsebenarnya sebuah objek. Jangan lakukan itu.

  • mencoba memohon metode pada referensi void *tipe akan membuat peringatan kompiler.

  • mencoba memohon metode pada idtipe hanya akan memperingatkan jika metode yang dipanggil belum dideklarasikan dalam @interfacedeklarasi apa pun yang dilihat oleh kompiler.

Dengan demikian, seseorang tidak boleh merujuk ke objek sebagai a void *. Demikian pula, seseorang harus menghindari menggunakan idvariabel yang diketik untuk merujuk ke objek. Gunakan referensi diketik kelas paling spesifik yang Anda bisa. Bahkan NSObject *lebih baik daripadaid karena kompiler dapat, paling tidak, memberikan validasi yang lebih baik dari pemanggilan metode terhadap referensi itu.

Penggunaan yang umum dan valid dari void * adalah sebagai referensi data buram yang dilewatkan melalui beberapa API lainnya.

Pertimbangkan sortedArrayUsingFunction: context:metode NSArray:

- (NSArray *)sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))comparator context:(void *)context;

Fungsi penyortiran akan dinyatakan sebagai:

NSInteger mySortFunc(id left, id right, void *context) { ...; }

Dalam hal ini, NSArray hanya meneruskan apa pun yang Anda berikan sebagai contextargumen untuk metode melalui sebagaicontext argumen. Ini adalah kumpulan data ukuran pointer yang buram, sejauh menyangkut NSArray, dan Anda bebas menggunakannya untuk tujuan apa pun yang Anda inginkan.

Tanpa fitur tipe penutupan dalam bahasa, ini adalah satu-satunya cara untuk membawa sebongkah data dengan fungsi. Contoh; jika Anda ingin mySortFunc () disortir secara kondisional sebagai case-sensitive atau case-insensitive, sementara juga masih aman untuk thread, Anda akan melewatkan indikator case-sensitive dalam konteks, kemungkinan mencari di jalan masuk dan jalan keluar.

Rapuh dan rawan kesalahan, tetapi satu-satunya cara.

Blok memecahkan ini - Blok adalah penutupan untuk C. Mereka tersedia di Dentang - http://llvm.org/ dan meresap di Snow Leopard ( http://developer.apple.com/library/ios/documentation/Performance /Reference/GCD_libdispatch_Ref/GCD_libdispatch_Ref.pdf ).

Bbum
sumber
Selain itu, saya cukup yakin bahwa iddiasumsikan merespons -retaindan -release, sedangkan a void*benar-benar buram bagi callee. Anda tidak dapat meneruskan pointer sewenang-wenang ke -performSelector:withObject:afterDelay:(itu mempertahankan objek), dan Anda tidak dapat berasumsi bahwa +[UIView beginAnimations:context:]akan mempertahankan konteks (delegasi animasi harus mempertahankan kepemilikan konteks; UIKit mempertahankan delegasi animasi).
tc.
3
Tidak ada asumsi yang dapat dibuat tentang apa yang idditanggapi. Sebuah iddapat dengan mudah merujuk ke instance kelas yang tidak inheren dari NSObject. Namun, secara praktis, pernyataan Anda paling cocok dengan perilaku dunia nyata; Anda tidak dapat mencampur <NSObject>kelas yang tidak menerapkan dengan API Foundation dan melangkah terlalu jauh, itu sudah pasti!
Bbum
2
Sekarang iddan Classjenis diperlakukan sebagai pointer objek yang dapat dipertahankan di bawah ARC. Jadi anggapan itu benar setidaknya di bawah ARC.
eonil
Apa itu "menuai prematur"?
Brad Thomas
1
@BradThomas Ketika seorang pengumpul sampah mengumpulkan memori sebelum program selesai dengannya.
Bbum
21

id adalah pointer ke objek C objektif, di mana void * adalah pointer ke apa pun.

id juga mematikan peringatan terkait dengan memanggil mthod yang tidak dikenal, jadi misalnya:

[(id)obj doSomethingWeirdYouveNeverHeardOf];

tidak akan memberikan peringatan biasa tentang metode yang tidak diketahui. Itu tentu saja akan menimbulkan pengecualian pada saat run time kecuali obj tidak ada atau benar-benar mengimplementasikan metode itu.

Seringkali Anda harus menggunakan NSObject*atau id<NSObject>dalam preferensi id, yang setidaknya mengkonfirmasi bahwa objek yang dikembalikan adalah objek Kakao, sehingga Anda dapat dengan aman menggunakan metode seperti mempertahankan / melepaskan / melepaskan otomatis di atasnya.

Peter N Lewis
sumber
2
Untuk pemanggilan metode, target jenis (id) akan menghasilkan peringatan jika metode yang ditargetkan tidak diumumkan di mana pun. Jadi, dalam contoh Anda, lakukanSomethingWeirdYouveNeverHeardOf harus dideklarasikan di suatu tempat agar tidak ada peringatan.
bbum
Anda benar, contoh yang lebih baik adalah sesuatu seperti StoragePolicy.
Peter N Lewis
@PeterNLewis saya tidak setuju, Often you should use NSObject*bukan id. Dengan menentukan NSObject*Anda sebenarnya secara eksplisit mengatakan bahwa objek tersebut adalah NSObject. Setiap pemanggilan metode ke objek akan menghasilkan peringatan, tetapi tidak ada pengecualian runtime selama objek itu memang merespons pemanggilan metode. Peringatan itu jelas menjengkelkan, jadi idlebih baik. Tentu Anda bisa lebih spesifik dengan misalnya mengatakan id<MKAnnotation>, yang dalam hal ini berarti apa pun objeknya, harus sesuai dengan protokol MKAnnotation.
pnizzle
1
Jika Anda akan menggunakan id <MKAnnotation>, maka Anda dapat menggunakan NSObject <MKAnnotation> *. Yang kemudian memungkinkan Anda untuk menggunakan salah satu metode di MKAnnotation dan salah satu metode di NSObject (yaitu, semua objek dalam hierarki kelas root NSObject normal), dan mendapatkan peringatan untuk hal lain, yang jauh lebih baik daripada tidak ada peringatan dan crash runtime.
Peter N Lewis
8

Jika suatu metode memiliki tipe pengembalian idAnda dapat mengembalikan objek Objective-C.

void berarti, metode ini tidak akan mengembalikan apa pun.

void *hanyalah sebuah pointer. Anda tidak akan dapat mengedit konten di alamat yang ditunjuk oleh pointer.

fphilipe
sumber
2
Karena ini berlaku untuk nilai pengembalian suatu metode, sebagian besar benar. Karena ini berlaku untuk mendeklarasikan variabel atau argumen, tidak cukup. Dan Anda selalu dapat melemparkan (void *) ke jenis yang lebih spesifik jika Anda ingin membaca / menulis konten - bukan merupakan ide yang baik untuk melakukannya.
bbum
8

idadalah pointer ke objek Objective-C. void *adalah pointer ke apa pun . Anda dapat menggunakannya void *sebagai ganti id, tetapi tidak disarankan karena Anda tidak akan pernah mendapatkan peringatan kompiler untuk apa pun.

Anda mungkin ingin melihat stackoverflow.com/questions/466777/whats-the-difference-between-dclaring-a-variable-id-and-nsobject dan unixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs -id.html .

Michael
sumber
1
Tidak terlalu. (void *) variabel yang diketik tidak dapat menjadi target pemanggilan metode sama sekali. Ini menghasilkan "peringatan: jenis penerima tidak valid 'void *'" dari kompiler.
bbum
@bbum: void *variabel yang diketik pasti bisa menjadi target pemanggilan metode - ini peringatan, bukan kesalahan. Tidak hanya itu, Anda dapat melakukan ini: int i = (int)@"Hello, string!";dan menindaklanjuti dengan: printf("Sending to an int: '%s'\n", [i UTF8String]);. Ini peringatan, bukan kesalahan (dan tidak disarankan, atau portabel). Tapi alasan mengapa Anda dapat melakukan hal-hal ini adalah semua C. dasar
JohnE
1
Maaf. Anda benar; itu adalah peringatan, bukan kesalahan. Saya hanya memperlakukan peringatan sebagai kesalahan selalu dan di mana-mana.
bbum
4
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

Kode di atas adalah dari objc.h, jadi sepertinya id adalah turunan dari objc_object struct dan isa pointer dapat mengikat dengan objek Objective C Class apa pun, sedangkan void * hanyalah pointer yang tidak diketik.

mendongkrak
sumber
2

Pemahaman saya adalah bahwa id merepresentasikan pointer ke objek sementara void * dapat menunjuk ke sesuatu yang benar-benar, asalkan Anda kemudian melemparkannya ke jenis yang ingin Anda gunakan sebagai

hhafez
sumber
Jika Anda melakukan casting dari (void *) ke beberapa jenis objek, termasuk id, Anda kemungkinan besar salah melakukannya. Ada alasan untuk melakukannya, tetapi mereka sedikit, jauh di antara, dan hampir selalu menunjukkan cacat desain.
bbum
1
kutipan "ada alasan untuk melakukannya, tetapi mereka sedikit, jauh di antara" benar itu. Itu tergantung situasi. Namun saya tidak akan membuat pernyataan selimut seperti "Anda kemungkinan besar salah melakukannya" tanpa konteks.
hhafez
Saya akan membuat pernyataan selimut; harus memburu dan memperbaiki terlalu banyak bug terkutuk karena casting ke tipe yang salah dengan void * di antaranya. Satu-satunya pengecualian adalah API berbasis panggilan balik yang mengambil argumen konteks * kosong yang kontraknya menyatakan bahwa konteksnya akan tetap tak tersentuh antara mengatur panggilan balik dan menerima panggilan balik.
bbum
0

Selain apa yang sudah dikatakan, ada perbedaan antara objek dan pointer yang terkait dengan koleksi. Misalnya, jika Anda ingin memasukkan sesuatu ke NSArray, Anda memerlukan objek (dari tipe "id"), dan Anda tidak dapat menggunakan pointer data mentah di sana (dari tipe "void *"). Anda dapat menggunakan [NSValue valueWithPointer:rawData]untuk mengonversi void *rawDdatake tipe "id" untuk menggunakannya di dalam koleksi. Secara umum "id" lebih fleksibel dan memiliki lebih banyak semantik yang terkait dengan objek yang melekat padanya. Ada lebih banyak contoh yang menjelaskan tipe id dari Objective C di sini .

battlmonstr
sumber