Różnica między wartością null, __nullable i _Nullable w celu-C


154

Wraz z Xcode 6.3 wprowadzono nowe adnotacje, aby lepiej wyrazić zamiar API w Objective-C (i oczywiście w celu zapewnienia lepszej obsługi Swift). Adnotacje te były oczywiście nonnull, nullablei null_unspecified.

Ale w przypadku Xcode 7 pojawia się wiele ostrzeżeń, takich jak:

We wskaźniku brakuje specyfikatora typu dopuszczalności wartości null (_Nonnull, _Nullable lub _Null_unspecified).

Oprócz tego Apple używa innego typu specyfikatorów wartości null, oznaczając ich kod C ( źródło ):

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

Podsumowując, mamy teraz te 3 różne adnotacje dopuszczalności wartości zerowej:

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

Mimo że wiem, dlaczego i gdzie użyć której adnotacji, trochę się pogubiłem, jakiego typu adnotacje mam użyć, gdzie i dlaczego. Oto, co mogłem zebrać:

  • Dla właściwości należy używać nonnull, nullable, null_unspecified.
  • Dla parametrów metody należy używać nonnull, nullable, null_unspecified.
  • Dla metod C powinno się używać __nonnull, __nullable, __null_unspecified.
  • W innych przypadkach, takich jak podwójne wskaźniki należy używać _Nonnull, _Nullable, _Null_unspecified.

Ale wciąż nie wiem, dlaczego mamy tak wiele adnotacji, które zasadniczo robią to samo.

Więc moje pytanie brzmi:

Jaka jest dokładna różnica między tymi adnotacjami, jak je poprawnie umieścić i dlaczego?


3
Przeczytałem ten post, ale nie wyjaśnia on różnicy i dlaczego mamy teraz 3 różne typy adnotacji i naprawdę chcę zrozumieć, dlaczego dodali trzeci typ.
— Bezprawny,

2
To naprawdę nie pomaga @ Cy-4AH i wiesz o tym. :)
— Bezległy

@Legoless, czy na pewno uważnie go przeczytałeś? wyjaśnia dokładnie, gdzie i jak należy ich używać, jakie są zakresy objęte audytem, ​​kiedy można użyć drugiego dla lepszej czytelności, ze względu na zgodność itp. itp. możesz nie wiedzieć, o co naprawdę chcesz zapytać, ale odpowiedź jest wyraźnie pod linkiem. to może tylko ja, ale nie sądzę, aby dalsze wyjaśnienia były potrzebne do wyjaśnienia ich celu, kopiowanie i wklejanie tego prostego wyjaśnienia tutaj, jako odpowiedzi, byłoby naprawdę niezręczne, jak sądzę. :(
— holex

2
Oczyszcza niektóre części, ale nie, nadal nie rozumiem, dlaczego nie mamy tylko pierwszych adnotacji. To tylko wyjaśnia, dlaczego przeszli z __nullable na _Nullable, ale nie dlaczego w ogóle potrzebujemy _Nullable, jeśli mamy nullable. Nie wyjaśnia też, dlaczego Apple nadal używa __nullable we własnym kodzie.
— Bezprawny

Odpowiedzi:


152

Z clang dokumentacji :

Kwalifikatory nullability (type) wyrażają, czy wartość danego typu wskaźnika może być null ( _Nullablekwalifikator), nie ma zdefiniowanego znaczenia dla null ( _Nonnullkwalifikator) lub dla którego cel null jest niejasny ( _Null_unspecifiedkwalifikator) . Ponieważ kwalifikatory dopuszczalności wartości null są wyrażane w systemie typów, są one bardziej ogólne niż atrybuty nonnulli returns_nonnull, umożliwiając wyrażenie (na przykład) wskaźnika dopuszczającego wartość null do tablicy wskaźników innych niż null. Kwalifikatory Nullability są zapisywane po prawej stronie wskaźnika, do którego mają zastosowanie.

, i

W celu-C istnieje alternatywna pisownia kwalifikatorów dopuszczalności wartości null, których można używać w metodach i właściwościach celu-C przy użyciu kontekstowych, niewidocznych słów kluczowych

Tak więc dla zwracanych metod i parametrów możesz użyć wersji z podwójnym podkreśleniem __nonnull/ __nullable/ __null_unspecifiedzamiast albo z pojedynczym podkreśleniem, albo zamiast wersji bez podkreślenia. Różnica polega na tym, że pojedyncze i podwójne podkreślone muszą być umieszczone po definicji typu, a niewidoczne przed definicją typu.

Zatem poniższe deklaracje są równoważne i poprawne:

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

Parametry:

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

W przypadku nieruchomości:

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

Sytuacja komplikuje się jednak, gdy w grę wchodzą podwójne wskaźniki lub bloki zwracające coś innego niż void, ponieważ te bez podkreślenia nie są tutaj dozwolone:

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

Podobnie jak w przypadku metod, które akceptują bloki jako parametry, należy pamiętać, że kwalifikator nonnull/ nullableodnosi się do bloku, a nie do jego zwracanego typu, dlatego poniższe są równoważne:

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

Jeśli blok ma wartość zwracaną, jesteś zmuszony do jednej z wersji z podkreśleniem:

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

Podsumowując, możesz użyć jednego z nich, o ile kompilator może określić element, do którego ma przypisać kwalifikator.


2
Wygląda na to, że wersje z podkreśleniami mogą być używane wszędzie, więc przypuszczam, że będę ich konsekwentnie używać, zamiast używać wersji z podkreśleniem w niektórych miejscach i bez podkreślenia w innych. Poprawny?
— Vaddadi Kartick

@KartickVaddadi tak, zgadza się, możesz konsekwentnie używać wersji z pojedynczym podkreśleniem lub z podwójnym podkreśleniem.
— Cristik

_Null_unspecifiedw Swift przekłada się to na opcjonalne? nieobowiązkowe czy co?
— Kochanie

1
@Honey _Null_unspecified jest importowany do Swift jako niejawnie
— rozpakowany

1
@Cristik aha. Wydaje mi się, że to jest jego wartość domyślna ... ponieważ kiedy nie określiłem, otrzymywałem niejawnie rozpakowane opcjonalne ...
— Honey

28

Z bloga Swift :

Ta funkcja została po raz pierwszy wydana w Xcode 6.3 ze słowami kluczowymi __nullable i __nonnull. Ze względu na potencjalne konflikty z bibliotekami innych firm zmieniliśmy je w Xcode 7 na _Nullable i _Nonnull, które widzisz tutaj. Jednak w celu zapewnienia zgodności z Xcode 6.3 wstępnie zdefiniowaliśmy makra __nullable i __nonnull, aby rozszerzyć je do nowych nazw.


4
Krótko mówiąc, wersje z pojedynczym i podwójnym podkreśleniem są identyczne.
— Vaddadi Kartick

4
Również udokumentowane w uwagach do wydania Xcode 7.0: „Nazwy kwalifikatorów podwójnie podkreślonych wartości null (__nullable, __nonnull i __null_unspecified) zostały przemianowane na użycie pojedynczego podkreślenia z wielką literą: _Nullable, _Nonnull i _Null_unspecified makra). Kompilator predefiniuje makra). mapowanie ze starych, podwójnie nieokreślonych nazw na nowe nazwy w celu zapewnienia zgodności ze źródłami. (21530726) ”
— Cosyn,

25

Bardzo podobał mi się ten artykuł , więc pokazuję tylko, co napisał autor: https://swiftunboxed.com/interop/objc-nullability-annotations/

  • null_unspecified:mosty do języka Swift niejawnie nieopakowanego opcjonalnego. To jest ustawienie domyślne .
  • nonnull: wartość nie będzie zerowa; łączy się ze zwykłym odniesieniem.
  • nullable: wartość może wynosić zero; mosty do opcjonalnego.
  • null_resettable: wartość nigdy nie może być zerowa podczas odczytu, ale można ustawić ją na zero, aby ją zresetować. Dotyczy tylko właściwości.

Powyższe notacje różnią się wtedy, czy używasz ich w kontekście właściwości czy funkcji / zmiennych:

Notacja wskaźników a notacja właściwości

Autor artykułu podał również fajny przykład:

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

Jest to bardzo przydatne

NS_ASSUME_NONNULL_BEGIN 

i zamykanie za pomocą

NS_ASSUME_NONNULL_END 

Spowoduje to zniweczenie potrzeby kodu na poziomie „nullibis” :-), ponieważ w pewnym sensie sensowne jest założenie, że wszystko jest inne niż null (lub nonnulllub _nonnulllub __nonnull), chyba że zaznaczono inaczej.

Niestety są też wyjątki od tej reguły ...

  • typedefnie zakłada się, że są __nonnull(uwaga, nonnullnie wydaje się działać, trzeba użyć brzydkiego przyrodniego brata)
  • id *potrzebuje wyraźnego nullibi, ale wow podatek od grzechu ( _Nullable id * _Nonnull<- zgadnij, co to znaczy ...)
  • NSError ** jest zawsze uznawana za dopuszczalną

Więc z wyjątkami od wyjątków i niespójnymi słowami kluczowymi wywołującymi tę samą funkcjonalność, być może podejście polega na użyciu brzydkich wersji __nonnull/ __nullable/ __null_unspecifiedi zamianie, gdy osoba zgodna narzeka ...? Może dlatego istnieją w nagłówkach Apple?

Co ciekawe, coś umieściło to w moim kodzie ... Brzydzę się podkreśleniami w kodzie (facet ze starej szkoły Apple C ++), więc jestem absolutnie pewien, że ich nie wpisałem, ale pojawiły się (jeden przykład z kilku):

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

Co ciekawsze, gdzie wstawiono __nullable jest błędne ... (eek @!)

Naprawdę chciałbym móc po prostu użyć wersji bez podkreślenia, ale najwyraźniej nie działa to z kompilatorem, ponieważ jest to oznaczone jako błąd:

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

2
Bez podkreślenia można używać tylko bezpośrednio po nawiasie otwierającym, tj. (Nie jest puste ... Jestem pewien, że tylko po to, aby uczynić życie bardziej interesującym.
— Elise van Looij

Jak sprawić, by identyfikator <> stał się niepusty? Wydaje mi się, że ta odpowiedź zawiera dużo wiedzy, ale brakuje jej jasności.
— fizzybear

1
@fizzybear muszę przyznać, że zazwyczaj mam odwrotne podejście. Wskaźniki są moim przyjacielem i od wczesnych lat 90-tych nie miałem problemów ze wskaźnikami „null” / „nil”. Żałuję, że nie mogę sprawić, by cała rzecz dopuszczalna / nie do zniesienia po prostu zniknęła. Ale do rzeczy, prawdziwa odpowiedź znajduje się w pierwszych 6 wierszach odpowiedzi. Ale jeśli chodzi o twoje pytanie: nie jest to coś, co bym zrobił (bez krytyki), więc po prostu nie wiem.
— William Cerniuk
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.