Sự khác biệt giữa nullable, __nullable và _Nullable trong Objective-C


154

Với Xcode 6.3, đã có các chú thích mới được giới thiệu để thể hiện rõ hơn ý định của API trong Objective-C (và để đảm bảo tất nhiên hỗ trợ Swift tốt hơn). Những chú thích đó là tất nhiên nonnull, nullablenull_unspecified.

Nhưng với Xcode 7, có rất nhiều cảnh báo xuất hiện như:

Con trỏ bị thiếu một công cụ xác định loại nullable (_Nonnull, _Nullable hoặc _Null_unspecified).

Ngoài ra, Apple sử dụng một loại công cụ xác định vô hiệu hóa khác, đánh dấu mã C ( nguồn ) của họ:

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

Vì vậy, để tổng hợp, chúng tôi hiện có 3 chú thích vô hiệu khác nhau này:

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

Mặc dù tôi biết tại sao và sử dụng chú thích nào, tôi hơi bối rối không biết nên sử dụng loại chú thích nào, ở đâu và tại sao. Đây là những gì tôi có thể thu thập:

  • Đối với tài sản tôi nên sử dụng nonnull, nullable, null_unspecified.
  • Đối với các thông số phương pháp tôi nên sử dụng nonnull, nullable, null_unspecified.
  • Đối với phương pháp C tôi nên sử dụng __nonnull, __nullable, __null_unspecified.
  • Đối với các trường hợp khác, chẳng hạn như con trỏ double tôi nên sử dụng _Nonnull, _Nullable, _Null_unspecified.

Nhưng tôi vẫn bối rối không biết tại sao chúng ta có quá nhiều chú thích về cơ bản lại làm điều tương tự.

Vì vậy, câu hỏi của tôi là:

Sự khác biệt chính xác giữa các chú thích đó là gì, làm thế nào để đặt chúng chính xác và tại sao?


3
Tôi đã đọc bài đăng đó, nhưng nó không giải thích được sự khác biệt và tại sao chúng ta có 3 loại chú thích khác nhau và tôi thực sự muốn hiểu tại sao họ lại tiếp tục thêm loại thứ ba.
Legoless

2
Điều này thực sự không giúp ích gì cho @ Cy-4AH và bạn biết điều đó. :)
Legoless

@Legoless, bạn có chắc là đã đọc kỹ không? nó giải thích chính xác vị trí và cách bạn nên sử dụng chúng, phạm vi được kiểm toán là gì, khi bạn có thể sử dụng phạm vi khác để dễ đọc hơn, lý do tương thích, v.v., v.v ... bạn có thể không biết bạn thực sự muốn hỏi gì, nhưng Câu trả lời rõ ràng dưới liên kết. có lẽ chỉ có tôi, nhưng tôi không cảm thấy rằng cần phải giải thích thêm bất cứ điều gì để giải thích mục đích của họ, sao chép và dán lời giải thích đơn giản đó vào đây như một câu trả lời ở đây sẽ rất khó xử, tôi đoán vậy. :(
holex

2
Nó làm rõ một số phần nhất định, nhưng không, tôi vẫn không hiểu tại sao chúng ta không có những chú thích đầu tiên. Nó chỉ giải thích lý do tại sao họ đi từ __nullable thành _Nullable, nhưng không phải tại sao chúng ta thậm chí cần _Nullable, nếu chúng ta có nullable. Và nó cũng không giải thích tại sao Apple vẫn sử dụng __nullable trong mã riêng của họ.
Legoless

Câu trả lời:


152

Từ clang tài liệu :

Các vòng loại nullable (loại) biểu thị cho dù giá trị của một loại con trỏ đã cho có thể là null ( _Nullablevòng loại), không có nghĩa được xác định cho null ( _Nonnullvòng loại) hoặc mục đích của null không rõ ràng ( _Null_unspecifiedvòng loại) . Bởi vì các vòng loại nullable được thể hiện trong hệ thống loại, chúng chung chung hơn các thuộc tính nonnullreturns_nonnullthuộc tính, cho phép một người thể hiện (ví dụ) một con trỏ nullable cho một mảng các con trỏ không khác nhau. Vòng loại Nullability được viết ở bên phải của con trỏ mà chúng áp dụng.

Trong Objective-C, có một cách viết thay thế cho các vòng loại nullable có thể được sử dụng trong các phương thức và thuộc tính của Objective-C bằng cách sử dụng các từ khóa không nhạy cảm theo ngữ cảnh

Vì vậy, đối với các trả về phương thức và các tham số, bạn có thể sử dụng các phiên bản được gạch dưới hai lần __nonnull/ __nullable/ __null_unspecifiedthay vì các phiên bản đơn lẻ hoặc thay vì các phiên bản không được bảo vệ. Sự khác biệt là những cái đơn lẻ và gấp đôi cần được đặt sau định nghĩa kiểu, trong khi những cái không được thiếu cần phải được đặt trước định nghĩa kiểu.

Do đó, các khai báo sau là tương đương và đúng:

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

Đối với tham số:

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

Đối với tài sản:

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

Tuy nhiên, mọi thứ trở nên phức tạp khi các con trỏ hoặc khối kép trả về một cái gì đó khác với khoảng trống có liên quan, vì những cái không gạch dưới không được phép ở đây:

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

Tương tự với các phương thức chấp nhận các khối làm tham số, xin lưu ý rằng nonnull/ nullablevòng loại áp dụng cho khối và không phải là kiểu trả về của nó, do đó, các cách sau là tương đương:

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

Nếu khối có giá trị trả về, thì bạn buộc phải tham gia một trong các phiên bản gạch dưới:

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

Để kết luận, bạn có thể sử dụng một trong hai, miễn là trình biên dịch có thể xác định mục để gán vòng loại.


2
Dường như các phiên bản gạch dưới có thể được sử dụng ở mọi nơi, vì vậy tôi cho rằng tôi sẽ sử dụng chúng một cách nhất quán, thay vì sử dụng các phiên bản dưới mức ở một số nơi và những nơi không có gạch dưới ở những nơi khác. Chính xác?
Vaddadi Kartick

@KartickVaddadi có, chính xác, bạn luôn có thể sử dụng một trong hai phiên bản chưa được ký tên hoặc hai phiên bản thiếu ký tự.
Cristik

_Null_unspecifiedtrong Swift này dịch sang tùy chọn? không bắt buộc hay sao?
Mật ong

1
@Honey _Null_unspecified được nhập vào Swift dưới dạng Tùy chọn không bị ràng buộc tùy chọn
Cristik

1
@Cristik aha. Tôi đoán đó là giá trị mặc định của nó ... bởi vì khi tôi không xác định, tôi đã hoàn toàn không được yêu cầu tùy chọn ...
Honey

28

Từ blog Swift :

Tính năng này được phát hành lần đầu tiên trong Xcode 6.3 với các từ khóa __nullable và __nonnull. Do các xung đột tiềm ẩn với các thư viện của bên thứ ba, chúng tôi đã thay đổi chúng trong Xcode 7 thành _Nullable và _Nullull bạn thấy ở đây. Tuy nhiên, để tương thích với Xcode 6.3, chúng tôi đã xác định trước các macro __nullable và __nonnull để mở rộng sang tên mới.


4
Tóm lại, các phiên bản gạch dưới đơn và đôi giống hệt nhau.
Vaddadi Kartick

4
Cũng được ghi lại trong ghi chú phát hành Xcode 7.0: "Các vòng loại vô hiệu hóa được gạch dưới hai lần (__nullable, __nonnull và __null_unspecified) đã được đổi tên để sử dụng một dấu gạch dưới đơn với chữ in hoa: _Nullable, _Nullull, và _Nullull ánh xạ từ tên kép không xác định cũ sang tên mới để tương thích nguồn. (21530726) "
Cosyn

25

Tôi thực sự thích bài viết này , vì vậy tôi chỉ hiển thị những gì tác giả đã viết: https://swiftunboxed.com/interop/objc-nullability-annotations/

  • null_unspecified:cầu nối đến một Swift tùy chọn không được bao bọc tùy chọn. Đây là mặc định .
  • nonnull: giá trị sẽ không bằng không; cầu đến một tài liệu tham khảo thường xuyên.
  • nullable: giá trị có thể bằng 0; cầu đến một tùy chọn.
  • null_resettable: giá trị không bao giờ có thể bằng 0 khi đọc, nhưng bạn có thể đặt nó thành 0 để đặt lại. Chỉ áp dụng cho các thuộc tính.

Các ký hiệu ở trên, sau đó khác nhau cho dù bạn sử dụng chúng trong ngữ cảnh của các thuộc tính hoặc hàm / biến:

Con trỏ so với ký hiệu thuộc tính

Tác giả của bài viết cũng cung cấp một ví dụ hay:

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

Rất tiện dụng là

NS_ASSUME_NONNULL_BEGIN 

và kết thúc với

NS_ASSUME_NONNULL_END 

Điều này sẽ vô hiệu hóa nhu cầu về mức mã 'nullibis' :-) vì sẽ hợp lý khi cho rằng mọi thứ đều không có giá trị ( nonnullhoặc _nonnullhoặc __nonnull) trừ khi có ghi chú khác.

Thật không may, cũng có ngoại lệ cho điều này ...

  • typedefs không được coi là __nonnull(lưu ý, nonnulldường như không hoạt động, phải sử dụng nó là anh em cùng cha khác mẹ xấu xí)
  • id *cần một nullibi rõ ràng nhưng wow thuế tội lỗi ( _Nullable id * _Nonnull<- đoán điều đó có nghĩa là gì ...)
  • NSError ** luôn luôn được coi là nullable

Vì vậy, với các ngoại lệ cho các ngoại lệ và các từ khóa không nhất quán gợi ra chức năng tương tự, có lẽ cách tiếp cận là sử dụng các phiên bản __nonnull/ __nullable/ __null_unspecifiedvà trao đổi xấu xí khi người khiếu nại phàn nàn ...? Có lẽ đó là lý do tại sao chúng tồn tại trong các tiêu đề của Apple?

Thật thú vị, một cái gì đó đã đưa nó vào mã của tôi ... Tôi ghê tởm mã dưới (anh chàng kiểu Apple C ++ cũ) nên tôi hoàn toàn chắc chắn rằng tôi đã không gõ những thứ này nhưng chúng xuất hiện (một ví dụ về một số):

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

Và thú vị hơn nữa, khi nó chèn __nullable là sai ... (eek @!)

Tôi thực sự muốn tôi chỉ có thể sử dụng phiên bản không gạch dưới nhưng dường như nó không bay với trình biên dịch vì điều này được gắn cờ là một lỗi:

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

2
Không gạch dưới chỉ có thể được sử dụng trực tiếp sau dấu ngoặc đơn mở, nghĩa là (không hoàn toàn ... Chỉ để làm cho cuộc sống thú vị hơn, tôi chắc chắn.
Elise van Looij

Làm cách nào để tạo id <> nonnull? Tôi cảm thấy câu trả lời này chứa rất nhiều kiến ​​thức nhưng nó thiếu rõ ràng.
fizzybear

1
@fizzybear thừa nhận tôi thường có cách tiếp cận ngược lại. Con trỏ là bạn của tôi và tôi không gặp vấn đề về con trỏ "null" / "nil" kể từ đầu những năm 90. Tôi ước tôi có thể làm cho toàn bộ điều vô giá trị / không có khả năng biến mất. Nhưng đến thời điểm, câu trả lời thực sự nằm trong 6 dòng đầu tiên của câu trả lời. Nhưng về câu hỏi của bạn: không phải điều gì tôi sẽ làm (không chỉ trích) nên tôi không biết.
William Cerniuk
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.