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?

Bezprawia
źródło
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.

Cristik
źródło
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.

Ben Thomas
źródło
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;
kgaidis
źródło
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 );
William Cerniuk
źródło
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