Di Objective-C mengapa saya harus memeriksa apakah self = [super init] tidak nol?

165

Saya punya pertanyaan umum tentang menulis metode init di Objective-C.

Saya melihatnya di mana-mana (kode Apple, buku, kode sumber terbuka, dll.) Bahwa metode init harus memeriksa apakah self = [super init] tidak nol sebelum melanjutkan dengan inisialisasi.

Template Apple default untuk metode init adalah:

- (id) init
{
    self = [super init];

    if (self != nil)
    {
        // your code here
    }

    return self;
}

Mengapa?

Maksud saya kapan init akan kembali nihil? Jika saya memanggil init di NSObject dan mendapat nil kembali, maka sesuatu pasti benar-benar kacau, bukan? Dan dalam hal ini, Anda mungkin bahkan tidak menulis program ...

Apakah benar-benar umum bahwa metode init kelas dapat mengembalikan nol? Jika demikian, dalam hal apa, dan mengapa?

Jasarien
sumber
1
Wil Shipley memposting artikel yang berkaitan dengan ini beberapa waktu lalu. [self = [bodoh init];] ( wilshipley.com/blog/2005/07/self-stupid-init.html ) Baca juga komentarnya, beberapa hal bagus.
Ryan Townshend
6
Anda bisa bertanya pada Wil Shipley atau Mike Ash atau Matt Gallagher . Bagaimanapun, itu adalah topik yang diperdebatkan. Tapi biasanya itu baik untuk tetap dengan idiom Apple ... itu Kerangka mereka, setelah semua.
jbrennan
1
Tampaknya Wil membuat kasus yang lebih besar karena tidak menetapkan ulang diri secara membabi buta selama init, mengetahui bahwa [super init] mungkin tidak mengembalikan receiver.
Jasarien
3
Wil telah mengubah pikirannya sejak jabatan itu awalnya dibuat.
bbum
5
Saya telah melihat pertanyaan ini beberapa waktu yang lalu dan baru saja menemukannya lagi. Sempurna. +1
Dan Rosenstark

Jawaban:

53

Sebagai contoh:

[[NSData alloc] initWithContentsOfFile:@"this/path/doesn't/exist/"];
[[NSImage alloc] initWithContentsOfFile:@"unsupportedFormat.sjt"];
[NSImage imageNamed:@"AnImageThatIsntInTheImageCache"];

... dan seterusnya. (Catatan: NSData mungkin melempar pengecualian jika file tidak ada). Ada beberapa area di mana pengembalian nol adalah perilaku yang diharapkan ketika masalah terjadi, dan karena ini merupakan praktik standar untuk memeriksa nol hampir sepanjang waktu, demi konsistensi.

iKenndac
sumber
10
Ya, tapi ini bukan DALAM metode init kelas masing-masing. NSData mewarisi dari NSObject. Apakah NSData memeriksa apakah [super init] mengembalikan nihil? Itulah yang saya tanyakan di sini. Maaf jika saya tidak jelas ...
Jasarien
9
Jika Anda mensubclass kelas-kelas tersebut, akan ada peluang nyata bahwa [super init] akan mengembalikan nol. Tidak semua kelas adalah subclass langsung dari NSObject. Tidak pernah ada jaminan bahwa itu tidak akan mengembalikan nol. Ini hanya praktik pengkodean defensif yang umumnya dianjurkan.
Chuck
7
Saya ragu NSObject init dapat mengembalikan nol dalam hal apa pun, pernah. Jika Anda kehabisan memori, alokasi akan gagal, tetapi jika berhasil, saya ragu init bisa gagal - NSObject bahkan tidak memiliki variabel instan kecuali Kelas. Di GNUStep, ini diimplementasikan sebagai "kembali sendiri", dan pembongkaran pada Mac terlihat sama. Semua ini, tentu saja, irrelevent - cukup ikuti idiom standar dan Anda tidak perlu khawatir tentang itu bisa atau tidak bisa.
Peter N Lewis
9
Saya tidak yakin untuk tidak mengikuti praktik terbaik. Namun, saya ingin tahu mengapa hal itu merupakan praktik terbaik. Seperti disuruh melompat dari menara. Anda tidak hanya melanjutkan dan melakukannya kecuali Anda tahu sebabnya. Apakah ada bantal besar dan lembut untuk mendarat di bagian bawah, dengan hadiah uang tunai yang besar? Jika saya tahu bahwa saya akan melompat. Jika tidak, saya tidak akan melakukannya. Saya tidak ingin hanya mengikuti latihan secara membabi buta tanpa tahu mengapa saya mengikutinya ...
Jasarien
4
Jika alokasi mengembalikan nihil, init dikirim ke nihil, yang akan selalu menghasilkan nihil, ini berakhir dengan diri sendiri nihil.
T.
50

Ungkapan khusus ini adalah standar karena ia berfungsi dalam semua kasus.

Meskipun tidak umum, akan ada kasus di mana ...

[super init];

... mengembalikan contoh yang berbeda, sehingga membutuhkan penugasan untuk diri sendiri.

Dan akan ada kasus-kasus di mana ia akan mengembalikan nil, sehingga membutuhkan pemeriksaan nil sehingga kode Anda tidak mencoba untuk menginisialisasi slot variabel instan yang tidak ada lagi.

Intinya adalah bahwa itu adalah pola yang benar yang didokumentasikan untuk digunakan dan, jika Anda tidak menggunakannya, Anda salah melakukannya.

Bbum
sumber
3
Apakah ini masih benar mengingat penentu nullability? Jika initializer superclass saya adalah non-null maka apakah benar-benar masih layak untuk memeriksa kekacauan tambahan? (Meskipun NSObject sendiri tampaknya tidak memiliki -initefeknya ...)
natevw
Apakah pernah ada kasus di mana [super init]mengembalikan nol ketika superclass langsung NSObject? Bukankah ini kasus di mana "semuanya rusak?"
Dan Rosenstark
1
@DanRosenstark Tidak jika NSObjectadalah superclass langsung. Tapi ... bahkan jika Anda mendeklarasikan NSObjectsebagai superclass langsung, sesuatu bisa saja telah dimodifikasi pada saat runtime sehingga NSObjectimplementasi initbukan apa yang sebenarnya disebut.
Bbum
1
Terima kasih banyak @bbum, ini benar-benar membantu saya mengarahkan beberapa perbaikan bug. Bagus untuk menyingkirkan beberapa hal!
Dan Rosenstark
26

Saya pikir, di sebagian besar kelas, jika nilai pengembalian dari [super init] adalah nihil dan Anda memeriksanya, seperti yang disarankan oleh praktik standar, dan kemudian kembali secara prematur jika nihil, pada dasarnya aplikasi Anda masih tidak akan berfungsi dengan benar. Jika Anda berpikir tentang hal itu, meskipun jika (self! = Nil) periksa ada, untuk operasi yang tepat dari kelas Anda, 99,99% dari waktu Anda benar - benar membutuhkan diri untuk menjadi nihil. Sekarang, misalkan, untuk alasan apa pun, [yang super init] lakukan kembali nihil, pada dasarnya cek Anda terhadap nihil pada dasarnya lewat uang hingga pemanggil kelas Anda, di mana ia kemungkinan akan gagal lagian, karena secara alami akan menganggap bahwa panggilan itu berhasil

Pada dasarnya, yang saya maksudkan adalah 99,99% dari waktu, if (self! = Nil) tidak membelikan Anda apa pun dalam hal ketahanan yang lebih besar, karena Anda hanya menyerahkan uang kepada penyerang Anda. Untuk benar-benar dapat menangani ini dengan kuat, Anda benar-benar perlu melakukan cek di seluruh hierarki panggilan Anda. Dan meskipun begitu, satu-satunya hal yang akan membeli Anda adalah aplikasi Anda akan gagal sedikit lebih bersih / kuat. Tetapi itu masih akan gagal.

Jika kelas perpustakaan secara sewenang-wenang memutuskan untuk mengembalikan nol sebagai akibat dari [super init], Anda cukup banyak, dan itu lebih merupakan indikasi bahwa penulis kelas perpustakaan membuat kesalahan implementasi.

Saya pikir ini lebih merupakan saran pengkodean lama, ketika aplikasi berjalan dalam memori yang jauh lebih terbatas.

Tetapi untuk kode level C, saya biasanya masih akan memeriksa nilai pengembalian malloc () terhadap sebuah pointer NULL. Sedangkan, untuk Objective-C, sampai saya menemukan bukti yang bertentangan, saya pikir saya biasanya akan melewatkan pemeriksaan if (self! = Nil). Mengapa ada perbedaan?

Karena, pada level C dan malloc, dalam beberapa kasus Anda benar-benar dapat pulih sebagian. Sedangkan saya pikir dalam Objective-C, dalam 99,99% kasus, jika [super init] mengembalikan nol, Anda pada dasarnya kacau, bahkan jika Anda mencoba menanganinya. Anda mungkin juga membiarkan aplikasi crash dan berurusan dengan akibatnya.

Gino
sumber
6
Diucapkan dengan baik. Saya yang kedua itu.
Roger CS Wernersson
3
+1 Saya sepenuhnya setuju. Hanya sebuah catatan kecil: Saya tidak percaya bahwa polanya adalah hasil dari waktu di mana alokasi lebih sering gagal. Alokasi biasanya sudah dilakukan pada saat init dipanggil. Jika alokasi gagal, init bahkan tidak akan dipanggil.
Nikolai Ruhe
8

Ini adalah semacam ringkasan komentar di atas.

Katakanlah superclass kembali nil. Apa yang akan terjadi

Jika Anda tidak mengikuti konvensi

Kode Anda akan macet di tengah initmetode Anda . (Kecuali jika inittidak ada yang penting)

Jika Anda mengikuti konvensi, tidak mengetahui bahwa superclass mungkin mengembalikan nol (kebanyakan orang berakhir di sini)

Kode Anda mungkin akan macet di beberapa titik nanti, karena instance Anda adalah nil, di mana Anda mengharapkan sesuatu yang berbeda. Atau program Anda akan berperilaku tak terduga tanpa menabrak. Oh sayang! Apakah anda menginginkan ini? Saya tidak tahu ...

Jika Anda mengikuti konvensi, rela mengizinkan subclass Anda untuk mengembalikan nol

Dokumentasi kode Anda (!) Harus dengan jelas menyatakan: "mengembalikan ... atau nihil", dan sisa kode Anda perlu dipersiapkan untuk menangani ini. Sekarang masuk akal.

pengguna123444555621
sumber
5
Poin yang menarik di sini, saya pikir, adalah bahwa opsi # 1 jelas lebih disukai daripada opsi # 2. Jika benar-benar ada keadaan di mana Anda mungkin ingin init subclass Anda mengembalikan nol, maka # 3 lebih disukai. Jika satu-satunya alasan yang akan terjadi adalah karena bug dalam kode Anda, maka gunakan # 1. Menggunakan opsi # 2 hanya menunda meledaknya aplikasi Anda sampai titik waktu berikutnya, dan dengan demikian membuat pekerjaan Anda ketika Anda melakukan debug kesalahan yang jauh lebih sulit. Ini seperti menangkap secara diam-diam pengecualian dan melanjutkan tanpa menanganinya.
Mark Amery
Atau Anda beralih ke cepat dan hanya menggunakan opsional
AndrewSB
7

Biasanya, jika kelas Anda berasal langsung dari NSObject, Anda tidak perlu melakukannya. Namun, kebiasaan yang baik untuk masuk, seolah-olah kelas Anda berasal dari kelas lain, inisialisasi mereka dapat kembali nil, dan jika demikian, inisialisasi Anda dapat menangkap itu dan bertindak dengan benar.

Dan ya, sebagai catatan, saya mengikuti praktik terbaik dan menulisnya di semua kelas saya, bahkan yang berasal langsung NSObject.

John Rudy
sumber
1
Dengan mengingat hal ini, apakah praktik yang baik untuk memeriksa nihil setelah menginisialisasi variabel dan sebelum memanggil fungsi di dalamnya? misFoo *bar = [[Foo alloc] init]; if (bar) {[bar doStuff];}
dev_does_software
Mewarisi dari NSObjecttidak menjamin itu -initmemberi Anda NSObjectjuga, jika menghitung runtimes eksotis seperti versi GNUstep lama (yang mengembalikan GSObject) jadi tidak peduli apa, cek dan tugas.
Maxthon Chan
3

Anda benar, Anda bisa sering hanya menulis [super init], tetapi itu tidak akan berhasil untuk subkelas apa pun. Orang lebih suka hanya menghafal satu baris kode standar dan menggunakannya sepanjang waktu, bahkan ketika itu hanya kadang-kadang diperlukan, dan dengan demikian kita mendapatkan standar if (self = [super init]), yang mengambil baik kemungkinan nihil dikembalikan dan kemungkinan objek selain selfdikembalikan memperhitungkan.

andyvn22
sumber
3

Kesalahan umum adalah menulis

self = [[super alloc] init];

yang mengembalikan instance dari superclass, yang BUKAN apa yang Anda inginkan dalam konstruktor / init subclass. Anda mendapatkan kembali objek yang tidak menanggapi metode subkelas, yang dapat membingungkan, dan menghasilkan kesalahan membingungkan tentang tidak menanggapi metode atau pengidentifikasi yang tidak ditemukan, dll.

self = [super init]; 

diperlukan jika kelas super memiliki anggota (variabel atau objek lain) untuk menginisialisasi terlebih dahulu sebelum mengatur anggota subclass. Kalau tidak, runtime objc menginisialisasi mereka semua ke 0 atau ke nol . ( tidak seperti ANSI C, yang sering mengalokasikan potongan memori tanpa membersihkannya sama sekali )

Dan ya, inisialisasi kelas dasar dapat gagal karena kesalahan kehabisan memori, komponen yang hilang, kegagalan perolehan sumber daya, dll. Jadi pemeriksaan untuk nil adalah bijaksana, dan membutuhkan waktu kurang dari beberapa milidetik.

Chris Reid
sumber
2

Ini untuk memeriksa apakah intialazation bekerja, pernyataan if mengembalikan true jika metode init tidak mengembalikan nil, jadi itu cara untuk memeriksa pembuatan objek bekerja dengan benar. Beberapa alasan saya bisa memikirkan bahwa init mungkin gagal mungkin metode init yang ditimpa bahwa kelas super tidak tahu atau semacamnya, saya tidak akan berpikir itu adalah yang biasa sekalipun. Tapi jika itu terjadi, lebih baik tidak ada yang terjadi kalau saya kira begitu selalu diperiksa ...

Daniel
sumber
itu, tetapi htey dipanggil bersama, apa yang terjadi jika mengalokasikan f?
Daniel
Saya membayangkan jika alokasi gagal, maka init akan dikirim ke nol daripada instance dari kelas apa pun yang Anda panggil init. Dalam hal ini, tidak ada yang akan terjadi, dan tidak ada kode yang akan dieksekusi untuk menguji apakah [super init] mengembalikan nihil.
Jasarien
2
Alokasi memori tidak selalu dilakukan dalam + alokasi. Pertimbangkan kasus cluster kelas; NSString tidak tahu subclass spesifik mana yang digunakan sampai initializer dipanggil.
bbum
1

Pada OS X, tidak mungkin -[NSObject init]gagal karena alasan memori. Hal yang sama tidak dapat dikatakan untuk iOS.

Juga, praktik yang baik untuk menulis ketika mensubclass sebuah kelas yang mungkin kembali nilkarena alasan apa pun.

MaddTheSane
sumber
2
Pada kedua iOS dan Mac OS -[NSObject init]adalah sangat tidak mungkin gagal karena alasan memori karena tidak mengalokasikan memori apapun.
Nikolai Ruhe
Saya kira maksudnya adalah alokasi, bukan init :)
Thomas Tempelmann