ความแตกต่างระหว่าง nullable, __nullable และ _Nullable ใน Objective-C


154

ด้วย Xcode 6.3 มีการเพิ่มคำอธิบายประกอบใหม่เพื่อแสดงเจตนาของ API ในObjective-C ได้ดีขึ้น (และเพื่อให้แน่ใจว่ารองรับ Swift ได้ดีขึ้นแน่นอน) คำอธิบายประกอบเหล่านั้นเป็นของหลักสูตรnonnull, และnullablenull_unspecified

แต่ด้วย Xcode 7 มีคำเตือนปรากฏขึ้นมากมายเช่น:

ตัวชี้ไม่มีตัวระบุประเภท nullability (_Nonnull, _Nullable หรือ _Null_unspecified)

นอกจากนั้น Apple ยังใช้ตัวระบุความสามารถในการทำให้เป็นโมฆะอีกประเภทหนึ่งโดยทำเครื่องหมายรหัส C (ที่มา ):

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

ดังนั้นเพื่อสรุปตอนนี้เรามีคำอธิบายประกอบแบบ nullability ที่แตกต่างกัน 3 แบบ:

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

แม้ว่าฉันจะรู้ว่าเพราะเหตุใดและที่ไหนที่จะใช้คำอธิบายประกอบใดฉันจะสับสนเล็กน้อยโดยประเภทของคำอธิบายประกอบที่ฉันควรใช้ที่ไหนและทำไม นี่คือสิ่งที่ฉันสามารถรวบรวมได้:

  • สำหรับคุณสมบัติของฉันควรใช้nonnull, ,nullablenull_unspecified
  • สำหรับพารามิเตอร์วิธีการที่ฉันควรใช้nonnull, ,nullablenull_unspecified
  • สำหรับวิธีการ C ฉันควรใช้__nonnull, ,__nullable__null_unspecified
  • สำหรับกรณีอื่น ๆ เช่นตัวชี้คู่ฉันควรใช้_Nonnull, ,_Nullable_Null_unspecified

แต่ฉันก็ยังงงว่าทำไมเราถึงมีคำอธิบายประกอบมากมายที่ทำในสิ่งเดียวกัน

ดังนั้นคำถามของฉันคือ:

อะไรคือความแตกต่างที่แน่นอนระหว่างคำอธิบายประกอบเหล่านั้นวิธีการวางอย่างถูกต้องและทำไม


3
ฉันได้อ่านโพสต์นั้นแล้ว แต่มันไม่ได้อธิบายความแตกต่างและทำไมเรามีคำอธิบายประกอบ 3 แบบในตอนนี้และฉันต้องการที่จะเข้าใจว่าทำไมพวกเขาถึงเพิ่มประเภทที่สาม
Legoless

2
สิ่งนี้ไม่ได้ช่วย @ Cy-4AH และคุณรู้ :)
Legoless

@Legoless คุณแน่ใจหรือว่าอ่านอย่างระมัดระวัง? มันอธิบายได้อย่างแม่นยำว่าคุณควรใช้ที่ใดและอย่างไรขอบเขตที่ได้รับการตรวจสอบเมื่อคุณสามารถใช้อีกอันเพื่อการอ่านที่ดีกว่าเหตุผลความเข้ากันได้ ฯลฯ ฯลฯ ... คุณอาจไม่ทราบว่าคุณต้องการถามอะไร คำตอบนั้นชัดเจนภายใต้ลิงก์ อาจจะเป็นแค่ฉัน แต่ฉันไม่รู้สึกว่าคำอธิบายเพิ่มเติมใด ๆ ที่จำเป็นสำหรับการอธิบายวัตถุประสงค์ของพวกเขาคัดลอกและวางคำอธิบายง่ายๆไว้ที่นี่เนื่องจากคำตอบที่นี่จะน่าอึดอัดใจจริงๆฉันเดา :(
holex

2
มันชัดเจนบางส่วน แต่ไม่ฉันยังไม่เข้าใจว่าทำไมเราไม่มีคำอธิบายประกอบแรก มันอธิบายว่าทำไมพวกเขาถึงเปลี่ยนจาก __nullable เป็น _Nullable แต่ไม่ใช่ว่าทำไมเราถึงต้องใช้ _Nullable ถ้าเรามี nullable และมันก็ไม่ได้อธิบายว่าทำไม Apple ยังคงใช้ __nullable ในรหัสของตัวเอง
Legoless

คำตอบ:


152

จากclang เอกสาร :

ตัวระบุความสามารถในการทำให้เป็นโมฆะ (ชนิด) แสดงว่าค่าของประเภทตัวชี้ที่ระบุอาจเป็นค่าว่าง (ตัวระบุ_Nullable) ไม่มีความหมายที่กำหนดไว้สำหรับโมฆะ (ตัวระบุ_Nonnull) หรือจุดประสงค์ของโมฆะนั้นไม่ชัดเจน (ตัวระบุ_Null_unspecified) . เนื่องจากตัวระบุความสามารถในการทำให้เป็นโมฆะจะแสดงอยู่ในระบบประเภทจึงมีความกว้างกว่าnonnullและreturns_nonnullคุณลักษณะเพื่อให้ตัวบ่งชี้ที่เป็นโมฆะไปยังอาร์เรย์ของพอยน์เตอร์ที่ไม่ใช่นัล ตัวระบุคุณสมบัติ Nullability ถูกเขียนทางด้านขวาของตัวชี้ที่ใช้

และ

ใน Objective-C มีตัวสะกดสำรองสำหรับ qualifier ของความสามารถทาง null ที่สามารถใช้ในเมธอดและคุณสมบัติของ Objective-C โดยใช้คีย์เวิร์ดที่คำนึงถึงบริบท

ดังนั้นสำหรับการส่งคืนเมธอดและพารามิเตอร์คุณสามารถใช้เวอร์ชัน__nonnull/ ขีดล่างคู่/ __nullable/ __null_unspecifiedแทนทั้งสองขีดเส้นใต้เดี่ยวหรือแทนอันที่ไม่ใช่ขีดล่าง ความแตกต่างก็คือต้องมีการขีดเส้นใต้เดี่ยวและคู่ต้องอยู่ภายใต้นิยามประเภทในขณะที่คนที่ไม่ใช่ขีดเส้นใต้จะต้องอยู่ก่อนคำนิยามประเภท

ดังนั้นการประกาศต่อไปนี้จะเทียบเท่าและถูกต้อง:

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

สำหรับพารามิเตอร์:

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

สำหรับคุณสมบัติ:

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

อย่างไรก็ตามสิ่งที่ซับซ้อนเมื่อพอยน์เตอร์คู่หรือบล็อกส่งคืนสิ่งที่แตกต่างจากโมฆะนั้นมีส่วนเกี่ยวข้องเนื่องจากไม่อนุญาตให้มีการขีดเส้นใต้ที่ไม่ใช่:

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

คล้ายกับวิธีที่ยอมรับบล็อกเป็นพารามิเตอร์โปรดทราบว่าnonnull/ nullablequalifier ใช้กับบล็อกไม่ใช่ประเภทส่งคืนดังนั้นสิ่งต่อไปนี้จะเทียบเท่า:

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

หากบล็อกมีค่าส่งคืนแสดงว่าคุณถูกบังคับให้ใช้เวอร์ชันขีดล่าง:

- (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

โดยสรุปคุณสามารถใช้รายการใดรายการหนึ่งได้ตราบใดที่คอมไพเลอร์สามารถกำหนดรายการเพื่อกำหนดตัวระบุให้


2
ดูเหมือนว่าเวอร์ชันขีดล่างสามารถใช้ได้ทุกที่ดังนั้นฉันจึงคิดว่าฉันจะใช้มันอย่างต่อเนื่องแทนที่จะใช้ขีดเส้นใต้ในบางสถานที่และรุ่นที่ไม่มีเครื่องหมายขีดล่างในที่อื่น ๆ แก้ไข?
Vaddadi Kartick

@KartickVaddadi ใช่ถูกต้องคุณสามารถใช้เครื่องหมายขีดล่างเดี่ยวหรือเครื่องหมายขีดเส้นใต้คู่ได้อย่างต่อเนื่อง
Cristik

_Null_unspecifiedใน Swift แปลว่าตัวเลือกนี้หรือไม่? ไม่ใช่ตัวเลือกหรืออะไร
ฮั

1
@Honey _Null_unspecified นำเข้าใน Swift โดยปริยาย Unwrapped ตัวเลือก
Cristik

1
@ Christik aha ฉันเดาว่ามันเป็นค่าเริ่มต้น ... เพราะเมื่อฉันไม่ได้ ระบุว่าฉันได้รับตัวเลือกที่ยังไม่ได้เปิดโดยปริยาย ...
ฮั

28

จากบล็อก Swift :

คุณลักษณะนี้เปิดตัวครั้งแรกใน Xcode 6.3 พร้อมด้วยคำหลัก __nullable และ __nonnull เนื่องจากความขัดแย้งที่อาจเกิดขึ้นกับห้องสมุดบุคคลที่สามเราได้เปลี่ยนพวกเขาใน Xcode 7 เป็น _Nullable และ _Nonnull ที่คุณเห็นที่นี่ อย่างไรก็ตามสำหรับความเข้ากันได้กับ Xcode 6.3 เราได้กำหนดมาโคร __nullable และ __nonnull เพื่อขยายไปยังชื่อใหม่


4
สั้นรุ่นที่ขีดเส้นใต้เดียวและสองเท่ากัน
Vaddadi Kartick

4
นอกจากนี้ยังมีการบันทึกไว้ในบันทึกย่อประจำรุ่น Xcode 7.0: "ตัวระบุขีดความสามารถลบล้างแบบสองขีด (__nullable, __nonnull, และ __null_unspecified) ตามลำดับคอมไพเลอร์ถูกเปลี่ยนชื่อให้ใช้ขีดล่างเดียวด้วยตัวพิมพ์ใหญ่: _Nullable, _Nullull และ _Null_unspecified ตามลำดับ) การจับคู่จากชื่อที่ไม่ระบุชื่อแบบเก่ากับชื่อใหม่สำหรับความเข้ากันได้ของแหล่งที่มา (21530726) "
Cosyn

25

ฉันชอบบทความนี้มากฉันแค่แสดงสิ่งที่ผู้เขียนเขียน: https://swiftunboxed.com/interop/objc-nullability-annotations/

  • null_unspecified:เชื่อมต่อกับสวิฟท์โดยไม่จำเป็น นี่คือการเริ่มต้น
  • nonnull: ค่าจะไม่เป็นศูนย์; เชื่อมโยงการอ้างอิงปกติ
  • nullable: ค่าสามารถเป็นศูนย์; เชื่อมโยงกับตัวเลือก
  • null_resettable: ค่าจะไม่เป็นศูนย์เมื่ออ่าน แต่คุณสามารถตั้งค่าเป็นศูนย์เพื่อรีเซ็ตได้ ใช้กับคุณสมบัติเท่านั้น

สัญลักษณ์ด้านบนนั้นแตกต่างกันไม่ว่าคุณจะใช้ในบริบทของคุณสมบัติหรือฟังก์ชั่น / ตัวแปร:

พอยน์เตอร์กับสัญกรณ์คุณสมบัติ

ผู้เขียนบทความนี้ยังให้ตัวอย่างที่ดี:

// 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;

12

มีประโยชน์มากคือ

NS_ASSUME_NONNULL_BEGIN 

และปิดด้วย

NS_ASSUME_NONNULL_END 

สิ่งนี้จะลบล้างความจำเป็นในระดับรหัส 'nullibis' :-) เนื่องจากมันเหมาะสมที่จะคิดว่าทุกอย่างไม่เป็นโมฆะ (หรือnonnullหรือ_nonnullหรือ__nonnull) เว้นแต่จะระบุไว้เป็นอย่างอื่น

น่าเสียดายที่มีข้อยกเว้นสำหรับสิ่งนี้เช่นกัน ...

  • typedefs จะไม่ถือว่าเป็น__nonnull(หมายเหตุnonnullดูเหมือนจะไม่ทำงานต้องใช้มันเป็นน้องชายที่น่าเกลียดครึ่ง)
  • id *ต้องการ nullibi ที่ชัดเจน แต่ว้าวบาป - ภาษี ( _Nullable id * _Nonnull<- เดาว่าความหมาย ... )
  • NSError ** ถือว่าเป็นโมฆะเสมอ

ดังนั้นด้วยข้อยกเว้นของข้อยกเว้นและคำหลักที่ไม่สอดคล้องกันทำให้ฟังก์ชั่นเดียวกันบางทีวิธีการคือการใช้เวอร์ชั่น__nonnull/ __nullable/ __null_unspecifiedและการแลกเปลี่ยนที่น่าเกลียดเมื่อผู้ร้องเรียนบ่น ... ? บางทีนั่นอาจเป็นเหตุผลว่าทำไมพวกเขาถึงมีอยู่ในส่วนหัวของ Apple?

น่าสนใจพอมีบางสิ่งที่ใส่ไว้ในรหัสของฉัน ... ฉันเกลียดการขีดเส้นใต้ในโค้ด (โรงเรียนเก่าสไตล์ Apple C ++) ดังนั้นฉันแน่ใจว่าฉันไม่ได้พิมพ์สิ่งเหล่านี้ แต่ปรากฏ (ตัวอย่างหนึ่งจากหลาย ๆ ):

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

และที่น่าสนใจยิ่งกว่าคือการใส่ __nullable นั้นผิด ... (eek @!)

ฉันหวังว่าฉันจะสามารถใช้เวอร์ชันที่ไม่ใช่ขีดล่าง แต่เห็นได้ชัดว่าไม่ได้บินไปกับคอมไพเลอร์เนื่องจากนี่เป็นข้อผิดพลาด:

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

2
ผู้ที่ไม่ใช่ขีดเส้นใต้สามารถใช้งานได้โดยตรงหลังจากวงเล็บเปิดคือ (ไม่ใช่ ... เพื่อให้ชีวิตน่าสนใจยิ่งขึ้นฉันแน่ใจ
Elise van Looij

ฉันจะสร้างรหัส <> ไม่เป็นโมฆะได้อย่างไร ฉันรู้สึกว่าคำตอบนี้มีความรู้มากมาย แต่ขาดความชัดเจน
fizzybear

1
@fizzybear เป็นที่ยอมรับโดยทั่วไปแล้วฉันใช้แนวทางตรงกันข้าม พอยน์เตอร์เป็นเพื่อนของฉันและฉันไม่เคยมีปัญหาตัวชี้ "โมฆะ" / "ไม่มี" มาตั้งแต่ต้นยุค 90 ฉันหวังว่าฉันจะทำให้สิ่งที่เป็นโมฆะ / ไร้ค่าทั้งหมดหายไป แต่จนถึงตอนนี้คำตอบที่แท้จริงคือใน 6 บรรทัดแรกของคำตอบคำตอบ แต่เกี่ยวกับคำถามของคุณ: ไม่ใช่สิ่งที่ฉันจะทำ (ไม่มีคำวิจารณ์) ดังนั้นฉันก็ไม่รู้
William Cerniuk
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.