Mengirim pesan ke nihil di Objective-C

107

Sebagai pengembang Java yang membaca dokumentasi Objective-C 2.0 Apple: Saya ingin tahu apa artinya " mengirim pesan ke nil " - apalagi bagaimana sebenarnya itu berguna. Mengambil kutipan dari dokumentasi:

Ada beberapa pola di Kakao yang memanfaatkan fakta ini. Nilai yang dikembalikan dari pesan menjadi nihil mungkin juga valid:

  • Jika metode mengembalikan objek, tipe penunjuk apa pun, skalar bilangan bulat apa pun yang ukurannya kurang dari atau sama dengan sizeof (void *), float, double, long double, atau long long, maka pesan yang dikirim ke nil mengembalikan 0 .
  • Jika metode mengembalikan struct, seperti yang didefinisikan oleh Mac OS X ABI Function Call Guide untuk dikembalikan dalam register, maka pesan yang dikirim ke nil mengembalikan 0,0 untuk setiap bidang dalam struktur data. Tipe data struct lainnya tidak akan diisi dengan angka nol.
  • Jika metode mengembalikan apa pun selain tipe nilai yang disebutkan di atas, nilai kembali dari pesan yang dikirim ke nil tidak ditentukan.

Apakah Java membuat otak saya tidak mampu membaca penjelasan di atas? Atau adakah sesuatu yang saya lewatkan yang akan membuat ini sejelas kaca?

Saya mendapatkan ide tentang pesan / penerima di Objective-C, saya hanya bingung tentang penerima yang kebetulan ada nil.

Ryan Delucchi
sumber
2
Saya juga memiliki latar belakang Java dan saya takut dengan fitur bagus ini pada awalnya, tetapi sekarang saya merasa benar-benar MENCINTAI !;
Valentin Radu
1
Terima kasih, itu pertanyaan yang bagus. Pernahkah Anda melihat manfaatnya? Menurut saya, ini adalah "bukan bug, fitur". Saya terus mendapatkan bug di mana Java hanya akan menampar saya dengan pengecualian, jadi saya tahu di mana masalahnya. Saya tidak senang memperdagangkan pengecualian penunjuk nol untuk menyimpan satu atau dua baris kode sepele di sana-sini.
Maciej Trybiło

Jawaban:

92

Saya rasa ini bisa dijelaskan dengan contoh yang dibuat-buat. Katakanlah Anda memiliki metode di Java yang mencetak semua elemen dalam ArrayList:

void foo(ArrayList list)
{
    for(int i = 0; i < list.size(); ++i){
        System.out.println(list.get(i).toString());
    }
}

Sekarang, jika Anda memanggil metode itu seperti: someObject.foo (NULL); Anda mungkin akan mendapatkan NullPointerException ketika mencoba mengakses daftar, dalam hal ini dalam panggilan ke list.size (); Sekarang, Anda mungkin tidak akan pernah memanggil someObject.foo (NULL) dengan nilai NULL seperti itu. Namun, Anda mungkin mendapatkan ArrayList Anda dari metode yang mengembalikan NULL jika mengalami beberapa kesalahan saat menghasilkan ArrayList seperti someObject.foo (otherObject.getArrayList ());

Tentu saja, Anda juga akan mendapat masalah jika melakukan hal seperti ini:

ArrayList list = NULL;
list.size();

Sekarang, di Objective-C, kami memiliki metode yang setara:

- (void)foo:(NSArray*)anArray
{
    int i;
    for(i = 0; i < [anArray count]; ++i){
        NSLog(@"%@", [[anArray objectAtIndex:i] stringValue];
    }
}

Sekarang, jika kita memiliki kode berikut:

[someObject foo:nil];

kami memiliki situasi yang sama di mana Java akan menghasilkan NullPointerException. Objek nil akan diakses pertama kali pada [anArray count] Namun, alih-alih melempar NullPointerException, Objective-C hanya akan mengembalikan 0 sesuai dengan aturan di atas, sehingga loop tidak akan berjalan. Namun, jika kita menyetel perulangan untuk menjalankan beberapa kali, maka pertama-tama kita mengirim pesan ke anArray di [anArray objectAtIndex: i]; Ini juga akan mengembalikan 0, tetapi karena objectAtIndex: mengembalikan pointer, dan pointer ke 0 adalah nil / NULL, NSLog akan diteruskan nil setiap kali melalui loop. (Meskipun NSLog adalah fungsi dan bukan metode, ia mencetak (null) jika meneruskan NSString nil.

Dalam beberapa kasus, lebih baik memiliki NullPointerException, karena Anda dapat langsung mengetahui bahwa ada sesuatu yang salah dengan program, tetapi kecuali Anda menangkap pengecualian, program akan macet. (Dalam C, mencoba untuk mendereferensi NULL dengan cara ini menyebabkan program macet.) Dalam Objective-C, itu malah menyebabkan perilaku run-time yang mungkin salah. Namun, jika Anda memiliki metode yang tidak rusak jika mengembalikan 0 / nil / NULL / struct zeroed, maka Anda tidak perlu memeriksa untuk memastikan objek atau parameternya nihil.

Michael Buckley
sumber
33
Mungkin perlu disebutkan bahwa perilaku ini telah menjadi subyek banyak perdebatan di komunitas Objective-C selama beberapa dekade terakhir. Pertukaran antara "keamanan" dan "kenyamanan" dievaluasi secara berbeda oleh orang yang berbeda.
Mark Bessey
3
Dalam praktiknya, ada banyak kesimetrian antara meneruskan pesan ke nil dan cara kerja Objective-C, terutama dalam fitur penunjuk lemah baru di ARC. Pointer yang lemah secara otomatis menjadi nol. Jadi rancang API Anda sehingga dapat merespons 0 / nil / NIL / NULL dll.
Cthutu
1
Saya pikir jika Anda melakukannya myObject->iVarakan crash, tidak peduli apakah itu C dengan atau tanpa objek. (maaf untuk
gravedig
3
@ 11684 Itu benar, tetapi ->bukan lagi operasi Objective-C tapi sangat C-isme generik.
bbum
1
The akar OSX baru-baru ini mengeksploitasi / tersembunyi backdoor api dapat diakses untuk semua pengguna (bukan hanya admin) karena pesan Nil obj-c ini.
dcow
51

Sebuah pesan niltidak apa-apa dan kembali nil, Nil, NULL, 0, atau 0.0.

Peter Hosey
sumber
41

Semua postingan lainnya benar, tapi mungkin konsep itulah yang terpenting di sini.

Dalam panggilan metode Objective-C, referensi objek apa pun yang dapat menerima selektor adalah target yang valid untuk pemilih tersebut.

Ini menghemat BANYAK "apakah objek target tipe X?" kode - selama objek penerima mengimplementasikan selector, tidak ada bedanya kelas apa itu! niladalah NSObject yang menerima pemilih apa pun - ia tidak melakukan apa - apa. Ini menghilangkan banyak kode "periksa nihil, jangan kirim pesan jika benar" juga. (Konsep "jika ia menerimanya, ia mengimplementasikannya" juga yang memungkinkan Anda untuk membuat protokol , yang agak mirip dengan antarmuka Java: deklarasi bahwa jika suatu kelas mengimplementasikan metode yang dinyatakan, maka ia sesuai dengan protokol.)

Alasannya adalah untuk menghilangkan kode monyet yang tidak melakukan apa pun kecuali membuat kompiler senang. Ya, Anda mendapatkan overhead untuk satu pemanggilan metode lagi, tetapi Anda menghemat waktu programmer , yang merupakan sumber daya yang jauh lebih mahal daripada waktu CPU. Selain itu, Anda menghilangkan lebih banyak kode dan lebih banyak kompleksitas bersyarat dari aplikasi Anda.

Memperjelas downvoters: Anda mungkin berpikir ini bukan cara yang baik, tetapi bagaimana bahasa diterapkan, dan ini adalah idiom pemrograman yang direkomendasikan di Objective-C (lihat kuliah pemrograman iPhone Stanford).

Joe McMahon
sumber
17

Artinya adalah bahwa runtime tidak menghasilkan kesalahan saat objc_msgSend dipanggil pada pointer nil; sebaliknya ia mengembalikan beberapa nilai (sering kali berguna). Pesan yang mungkin memiliki efek samping tidak melakukan apa-apa.

Ini berguna karena sebagian besar nilai default lebih sesuai daripada kesalahan. Sebagai contoh:

[someNullNSArrayReference count] => 0

Yaitu, nil tampaknya menjadi array kosong. Menyembunyikan referensi NSView nihil tidak melakukan apa pun. Berguna, ya?

Kaya
sumber
12

Dalam kutipan dari dokumentasi, terdapat dua konsep yang berbeda - mungkin akan lebih baik jika dokumentasinya dibuat lebih jelas:

Ada beberapa pola di Kakao yang memanfaatkan fakta ini.

Nilai yang dikembalikan dari pesan menjadi nihil mungkin juga valid:

Yang pertama mungkin lebih relevan di sini: biasanya dapat mengirim pesan untuk nilmembuat kode lebih mudah - Anda tidak perlu memeriksa nilai null di mana pun. Contoh kanonik mungkin adalah metode pengakses:

- (void)setValue:(MyClass *)newValue {
    if (value != newValue) { 
        [value release];
        value = [newValue retain];
    }
}

Jika pengiriman pesan ke niltidak valid, metode ini akan menjadi lebih kompleks - Anda harus memiliki dua pemeriksaan tambahan untuk memastikan valuedan newValuetidak nilsebelum mengirim pesan kepada mereka.

Titik terakhir (nilai yang dikembalikan dari pesan ke niljuga biasanya valid), meskipun, menambahkan efek pengali ke titik sebelumnya. Sebagai contoh:

if ([myArray count] > 0) {
    // do something...
}

Kode ini sekali lagi tidak memerlukan pemeriksaan nilnilai, dan mengalir secara alami ...

Semua ini mengatakan, fleksibilitas tambahan yang dapat mengirim pesan nilmemang datang dengan biaya tertentu. Ada kemungkinan bahwa pada tahap tertentu Anda akan menulis kode yang gagal dengan cara yang aneh karena Anda tidak memperhitungkan kemungkinan suatu nilai nil.

mmalc
sumber
12

Dari Greg Parker 's situs :

Jika menjalankan LLVM Compiler 3.0 (Xcode 4.2) atau yang lebih baru

Pesan ke nihil dengan tipe kembali | kembali
Integer hingga 64 bit | 0
Floating-point hingga long double | 0.0
Pointer | nol
Struktur | {0}
Jenis _Complex apa pun | {0, 0}
Heath Borders
sumber
9

Ini berarti seringkali tidak perlu memeriksa objek nihil di mana-mana untuk keamanan - terutama:

[someVariable release];

atau, sebagaimana disebutkan, berbagai metode hitung dan panjang semuanya mengembalikan 0 saat Anda mendapatkan nilai nihil, jadi Anda tidak perlu menambahkan pemeriksaan ekstra untuk nil seluruhnya:

if ( [myString length] > 0 )

atau ini:

return [myArray count]; // say for number of rows in a table
Kendall Helmstetter Gelner
sumber
Ingatlah bahwa sisi lain dari koin adalah potensi bug seperti "if ([myString length] == 1)"
hatfinch
Bagaimana itu bug? [myString length] mengembalikan nol (nil) jika myString nihil ... satu hal yang menurut saya mungkin menjadi masalah adalah [myView frame] yang menurut saya dapat memberi Anda sesuatu yang aneh jika myView nihil.
Kendall Helmstetter Gelner
Jika Anda merancang kelas dan metode Anda dengan konsep bahwa nilai default (0, nil, NO) berarti "tidak berguna", ini adalah alat yang ampuh. Saya tidak perlu memeriksa senar saya nol sebelum memeriksa panjangnya. Bagi saya string kosong sama tidak berguna dan string nol saat saya memproses teks. Saya juga seorang pengembang Java dan saya tahu puritan Java akan menghindari ini tetapi ini menghemat banyak pengkodean.
Jason Fuerstenberg
6

Jangan berpikir tentang "penerima menjadi nihil"; Saya setuju, bahwa adalah cukup aneh. Jika Anda mengirim pesan ke nihil, tidak ada penerima. Anda hanya mengirim pesan ke nol.

Cara mengatasinya adalah perbedaan filosofis antara Java dan Objective-C: di Java, itu adalah kesalahan; di Objective-C, ini adalah no-op.

benzado
sumber
Ada pengecualian untuk perilaku tersebut di java, jika Anda memanggil fungsi statis pada null, itu sama dengan memanggil fungsi tersebut pada kelas waktu kompilasi variabel (tidak masalah jika nilainya null).
Roman A. Taycher
6

Pesan ObjC yang dikirim ke nihil dan yang nilai kembaliannya memiliki ukuran lebih besar dari sizeof (void *) menghasilkan nilai yang tidak ditentukan pada prosesor PowerPC. Selain itu, pesan ini menyebabkan nilai yang tidak ditentukan dikembalikan di bidang struct yang ukurannya lebih besar dari 8 byte pada prosesor Intel juga. Vincent Gable telah menggambarkan hal ini dengan baik dalam postingan blognya

Nikita Zhuk
sumber
6

Saya rasa tidak ada jawaban lain yang menyebutkan ini dengan jelas: jika Anda terbiasa dengan Java, Anda harus ingat bahwa meskipun Objective-C di Mac OS X memiliki dukungan penanganan pengecualian, ini adalah fitur bahasa opsional yang dapat digunakan. dinyalakan / dimatikan dengan bendera kompilator. Dugaan saya adalah bahwa desain "mengirim pesan ke nilaman" ini mendahului dimasukkannya dukungan penanganan pengecualian dalam bahasa dan dilakukan dengan tujuan yang sama: metode dapat kembali niluntuk menunjukkan kesalahan, dan sejak mengirim pesan ke nilbiasanya kembalinilpada gilirannya, ini memungkinkan indikasi kesalahan menyebar melalui kode Anda sehingga Anda tidak perlu memeriksanya di setiap pesan. Anda hanya perlu memeriksanya pada titik-titik yang penting. Saya pribadi berpikir propagasi & penanganan pengecualian adalah cara yang lebih baik untuk mengatasi tujuan ini, tetapi tidak semua orang setuju dengan itu. (Di sisi lain, saya misalnya tidak menyukai persyaratan Java pada Anda yang harus mendeklarasikan pengecualian apa yang mungkin dilontarkan metode, yang sering kali memaksa Anda untuk menyebarkan deklarasi pengecualian secara sintaksis ke seluruh kode Anda; tetapi itu adalah diskusi lain.)

Saya telah memposting jawaban yang serupa, tetapi lebih panjang, untuk pertanyaan terkait "Apakah menegaskan bahwa setiap pembuatan objek berhasil diperlukan di Objective C?" jika Anda ingin lebih detail.

Rinzwind
sumber
Saya tidak pernah berpikir seperti itu. Ini tampaknya menjadi fitur yang sangat nyaman.
mk12
2
Tebakan bagus, tetapi secara historis tidak akurat tentang mengapa keputusan itu dibuat. Penanganan pengecualian sudah ada dalam bahasa sejak awal, meskipun penangan pengecualian asli cukup primitif dibandingkan dengan idiom modern. Nil-eats-message adalah pilihan desain sadar yang diturunkan dari perilaku opsional objek Nil di Smalltalk. Ketika NeXTSTEP API asli dirancang, perangkaian metode cukup umum dan nilsering digunakan untuk menghubungkan arus pendek rantai menjadi NO-op.
bbum
2

C tidak mewakili apa-apa sebagai 0 untuk nilai primitif, dan NULL untuk pointer (yang setara dengan 0 dalam konteks pointer).

Objective-C dibangun di atas representasi C tentang apa-apa dengan menambahkan nol. nil adalah penunjuk objek ke ketiadaan. Meskipun secara semantik berbeda dari NULL, mereka secara teknis setara satu sama lain.

NSObjects yang baru dialokasikan memulai hidup dengan isinya disetel ke 0. Ini berarti bahwa semua penunjuk yang dimiliki objek ke objek lain dimulai sebagai nil, jadi tidak perlu, misalnya, mengatur self. (Asosiasi) = nil dalam metode init.

Namun, perilaku nil yang paling menonjol adalah bahwa ia dapat mengirim pesan ke sana.

Dalam bahasa lain, seperti C ++ (atau Java), ini akan merusak program Anda, tetapi di Objective-C, memanggil metode pada nil mengembalikan nilai nol. Ini sangat menyederhanakan ekspresi, karena menghilangkan kebutuhan untuk memeriksa nol sebelum melakukan apa pun:

// For example, this expression...
if (name != nil && [name isEqualToString:@"Steve"]) { ... }

// ...can be simplified to:
if ([name isEqualToString:@"Steve"]) { ... }

Menyadari cara kerja nil di Objective-C memungkinkan kemudahan ini menjadi fitur, dan bukan bug yang bersembunyi di aplikasi Anda. Pastikan untuk menjaga dari kasus di mana nilai nihil tidak diinginkan, baik dengan memeriksa dan kembali lebih awal untuk gagal secara diam-diam, atau menambahkan NSParameterAssert untuk membuat pengecualian.

Sumber: http://nshipster.com/nil/ https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html (Mengirim Pesan ke nihil).

Zee
sumber