Perbedaan antara nullable, __nullable dan _Nullable di Objective-C

154

Dengan Xcode 6.3 ada anotasi baru yang diperkenalkan untuk lebih mengekspresikan maksud API di Objective-C (dan untuk memastikan dukungan Swift yang lebih baik tentu saja). Penjelasan itu tentu saja nonnull, nullabledan null_unspecified.

Tetapi dengan Xcode 7, ada banyak peringatan yang muncul seperti:

Pointer tidak memiliki specifier tipe nullability (_Nonnull, _Nullable atau _Null_unspecified).

Selain itu, Apple menggunakan jenis penentu nullability lain, menandai kode C ( sumber ) mereka:

CFArrayRef __nonnull CFArrayCreate(
CFAllocatorRef __nullable allocator, const void * __nonnull * __nullable values, CFIndex numValues, const CFArrayCallBacks * __nullable callBacks);

Jadi, singkatnya, kami sekarang memiliki 3 anotasi pembatalan yang berbeda ini:

  • nonnull, nullable,null_unspecified
  • _Nonnull, _Nullable,_Null_unspecified
  • __nonnull, __nullable,__null_unspecified

Meskipun saya tahu mengapa dan di mana harus menggunakan anotasi yang mana, saya agak bingung dengan jenis anotasi mana yang harus saya gunakan, di mana dan mengapa. Inilah yang bisa saya kumpulkan:

  • Untuk properti saya harus menggunakan nonnull, nullable, null_unspecified.
  • Untuk metode parameter saya harus menggunakan nonnull, nullable, null_unspecified.
  • Untuk metode C saya harus menggunakan __nonnull, __nullable, __null_unspecified.
  • Untuk kasus lain, seperti ganda pointer saya harus menggunakan _Nonnull, _Nullable, _Null_unspecified.

Tetapi saya masih bingung mengapa kita memiliki begitu banyak anotasi yang pada dasarnya melakukan hal yang sama.

Jadi pertanyaan saya adalah:

Apa perbedaan persis antara anotasi tersebut, cara menempatkannya dengan benar dan mengapa?

Tanpa hukum
sumber
3
Saya sudah membaca posting itu, tetapi tidak menjelaskan perbedaannya dan mengapa kami memiliki 3 jenis anotasi yang berbeda sekarang dan saya benar-benar ingin memahami mengapa mereka terus menambahkan jenis ketiga.
Legoless
2
Ini benar-benar tidak membantu @ Cy-4AH dan Anda tahu itu. :)
Legoless
@Legoless, Anda yakin sudah membacanya dengan cermat? itu menjelaskan dengan tepat di mana dan bagaimana Anda harus menggunakannya, apa saja ruang lingkup yang diaudit, ketika Anda dapat menggunakan yang lain untuk keterbacaan yang lebih baik, alasan kompatibilitas, dll, dll ... Anda mungkin tidak tahu apa yang sebenarnya ingin Anda tanyakan, tetapi jawabannya jelas di bawah tautan. mungkin hanya saya, tetapi saya tidak merasa bahwa penjelasan lebih lanjut diperlukan untuk menjelaskan tujuan mereka, menyalin dan menempelkan penjelasan sederhana itu di sini karena jawaban di sini akan benar-benar canggung, saya kira. :(
holex
2
Itu jelas bagian-bagian tertentu, tapi tidak, saya masih tidak mengerti mengapa kita tidak hanya memiliki anotasi pertama Itu hanya menjelaskan mengapa mereka beralih dari __nullable ke _Nullable, tetapi tidak mengapa kita bahkan perlu _Nullable, jika kita memiliki nullable. Dan itu juga tidak menjelaskan mengapa Apple masih menggunakan __nullable dalam kode mereka sendiri.
Legoless

Jawaban:

152

Dari clang dokumentasi :

Kualifikasi nullability (type) menyatakan apakah nilai dari tipe pointer yang diberikan dapat null ( _Nullablekualifikasi), tidak memiliki makna yang ditentukan untuk null ( _Nonnullkualifikasi), atau yang tujuan null tidak jelas ( _Null_unspecifiedkualifikasi) . Karena kualifikasi nullability diekspresikan dalam sistem tipe, mereka lebih umum daripada atribut nonnulldan returns_nonnull, memungkinkan seseorang untuk mengekspresikan (misalnya) pointer nullable ke array pointer nonnull. Kualifikasi yang tidak dapat ditulisi ditulis di sebelah kanan penunjuk yang mereka terapkan.

, dan

Di Objective-C, ada ejaan alternatif untuk kualifikasi nullability yang dapat digunakan dalam metode dan properti Objective-C menggunakan kata kunci yang peka terhadap konteks, non-garis bawah

Jadi untuk pengembalian metode dan parameter, Anda dapat menggunakan versi bergaris bawah ganda __nonnull/ __nullable/ __null_unspecifiedalih-alih yang bertanda tunggal, atau bukan yang bertanda bawah. Perbedaannya adalah bahwa yang bergaris bawah tunggal dan ganda perlu ditempatkan setelah definisi tipe, sedangkan yang bergaris bawah harus ditempatkan sebelum definisi tipe.

Dengan demikian, deklarasi berikut ini setara dan benar:

- (nullable NSNumber *)result
- (NSNumber * __nullable)result
- (NSNumber * _Nullable)result

Untuk parameter:

- (void)doSomethingWithString:(nullable NSString *)str
- (void)doSomethingWithString:(NSString * _Nullable)str
- (void)doSomethingWithString:(NSString * __nullable)str

Untuk properti:

@property(nullable) NSNumber *status
@property NSNumber *__nullable status
@property NSNumber * _Nullable status

Namun hal-hal menyulitkan ketika pointer ganda atau blok mengembalikan sesuatu yang berbeda dari kekosongan terlibat, karena yang non-garis bawah tidak diperbolehkan di sini:

- (void)compute:(NSError *  _Nullable * _Nullable)error
- (void)compute:(NSError *  __nullable * _Null_unspecified)error;
// and all other combinations

Mirip dengan metode yang menerima blok sebagai parameter, harap perhatikan bahwa nonnull/ nullablekualifikasi berlaku untuk blok, dan bukan tipe kembalinya, dengan demikian yang berikut ini setara:

- (void)executeWithCompletion:(nullable void (^)())handler
- (void)executeWithCompletion:(void (^ _Nullable)())handler
- (void)executeWithCompletion:(void (^ __nullable)())handler

Jika blok memiliki nilai kembali, maka Anda dipaksa ke salah satu versi garis bawah:

- (void)convertObject:(nullable id __nonnull (^)(nullable id obj))handler
- (void)convertObject:(id __nonnull (^ _Nullable)())handler
- (void)convertObject:(id _Nonnull (^ __nullable)())handler
// the method accepts a nullable block that returns a nonnull value
// there are some more combinations here, you get the idea

Sebagai kesimpulan, Anda bisa menggunakan yang mana saja, asalkan kompiler dapat menentukan item untuk menetapkan kualifikasi.

Cristik
sumber
2
Tampaknya versi garis bawah dapat digunakan di mana-mana, jadi saya kira saya akan menggunakannya secara konsisten, daripada menggunakan versi garis bawah di beberapa tempat dan yang tanpa garis bawah di tempat lain. Benar?
Vaddadi Kartick
@KartickVaddadi ya, benar, Anda dapat secara konsisten menggunakan versi garis bawah tunggal, atau versi garis bawah ganda.
Cristik
_Null_unspecifieddi Swift ini artinya opsional? non-opsional atau apa?
Sayang
1
@Honey _Null_unspecified diimpor dalam Swift sebagai Pilihan yang Tidak
Dibungkus
1
@Cristik aha. Saya rasa itu adalah nilai standarnya ... karena ketika saya tidak menentukan saya mendapatkan opsional yang terbuka secara implisit ...
Honey
28

Dari blog Swift :

Fitur ini pertama kali dirilis dalam Xcode 6.3 dengan kata kunci __nullable dan __nonnull. Karena kemungkinan konflik dengan perpustakaan pihak ketiga, kami telah mengubahnya di Xcode 7 menjadi _Nullable dan _Nonnull yang Anda lihat di sini. Namun, untuk kompatibilitas dengan Xcode 6.3 kami telah menetapkan makro __nullable dan __nullull untuk memperluas ke nama-nama baru.

Ben Thomas
sumber
4
Singkatnya, versi garis bawah tunggal dan ganda sama.
Vaddadi Kartick
4
Juga didokumentasikan dalam catatan rilis Xcode 7.0: "Kualifikasi nullability bergaris bawah ganda (__nullable, __nonnull, dan __null_unspecified) telah diganti nama untuk menggunakan garis bawah tunggal dengan huruf kapital: _Nullable, _Nonnull, dan _Null_unspecified, masing-masing). pemetaan dari nama ganda yang tidak ditentukan ke nama baru untuk kompatibilitas sumber. (21530726) "
Cosyn
25

Saya sangat menyukai artikel ini , jadi saya hanya menunjukkan apa yang penulis tulis: https://swiftunboxed.com/interop/objc-nullability-annotations/

  • null_unspecified:menjembatani ke Swift, pilihan yang secara implisit terbuka. Ini standarnya .
  • nonnull: nilai tidak akan nol; menjembatani ke referensi reguler.
  • nullable: nilainya bisa nol; menjembatani ke opsional.
  • null_resettable: nilai tidak pernah dapat nol saat dibaca, tetapi Anda dapat mengaturnya ke nil untuk mengatur ulang. Berlaku hanya untuk properti.

Notasi di atas, kemudian berbeda apakah Anda menggunakannya dalam konteks properti atau fungsi / variabel:

Notasi Pointer vs. Properties

Penulis artikel juga memberikan contoh yang bagus:

// property style
@property (nonatomic, strong, null_resettable) NSString *name;

// pointer style
+ (NSArray<NSView *> * _Nullable)interestingObjectsForKey:(NSString * _Nonnull)key;

// these two are equivalent!
@property (nonatomic, strong, nullable) NSString *identifier1;
@property (nonatomic, strong) NSString * _Nullable identifier2;
kgaidis
sumber
12

Sangat berguna

NS_ASSUME_NONNULL_BEGIN 

dan ditutup dengan

NS_ASSUME_NONNULL_END 

Ini akan meniadakan perlunya level kode 'nullibis' :-) karena agak masuk akal untuk menganggap bahwa semuanya adalah non-null (atau nonnullatau _nonnullatau __nonnull) kecuali dinyatakan sebaliknya.

Sayangnya ada pengecualian untuk ini juga ...

  • typedefs tidak dianggap __nonnull(catatan, nonnulltampaknya tidak berfungsi, harus menggunakannya saudara tiri jelek)
  • id *membutuhkan nullibi eksplisit tetapi wow pajak dosa ( _Nullable id * _Nonnull<- tebak apa artinya ...)
  • NSError ** selalu dianggap nullable

Jadi dengan pengecualian untuk pengecualian dan kata kunci yang tidak konsisten memunculkan fungsi yang sama, mungkin pendekatannya adalah dengan menggunakan versi jelek __nonnull/ __nullable/ __null_unspecifieddan bertukar ketika kompiler mengeluh ...? Mungkin itu sebabnya mereka ada di header Apple?

Cukup menarik, sesuatu memasukkannya ke dalam kode saya ... Saya benci menggarisbawahi dalam kode (Apple gaya lama C + + guy) jadi saya benar-benar yakin saya tidak mengetik ini tetapi mereka muncul (salah satu contoh beberapa):

typedef void ( ^ DidReceiveChallengeBlock ) ( NSURLSessionAuthChallengeDisposition disposition,
                                          NSURLCredential * __nullable credential );

Dan yang lebih menarik, di mana ia memasukkan __nullable salah ... (eek @!)

Saya benar-benar berharap saya bisa menggunakan versi non-garis bawah tetapi ternyata itu tidak terbang dengan kompiler karena ini ditandai sebagai kesalahan:

typedef void ( ^ DidReceiveChallengeBlock ) ( NSURLSessionAuthChallengeDisposition disposition,
                                          NSURLCredential * nonnull  credential );
William Cerniuk
sumber
2
Non-garis bawah hanya dapat digunakan secara langsung setelah tanda kurung buka, yaitu (nonnull ... Hanya untuk membuat hidup lebih menarik, saya yakin.
Elise van Looij
Bagaimana cara saya membuat id <> nonnull? Saya merasa jawaban ini mengandung banyak pengetahuan tetapi kurang jelas.
fizzybear
1
@fizzybear memang saya biasanya mengambil pendekatan yang berlawanan. Pointer adalah teman saya dan saya belum memiliki masalah pointer "null" / "nil" sejak awal 90-an. Saya berharap saya bisa membuat semuanya nullable / nilable pergi begitu saja. Tapi to the point, jawaban sebenarnya ada di 6 baris pertama dari jawaban jawaban. Tetapi tentang pertanyaan Anda: bukan sesuatu yang akan saya lakukan (tidak ada kritik) jadi saya tidak tahu.
William Cerniuk