Différence entre nullable, __nullable et _Nullable en Objective-C

154

Avec Xcode 6.3, de nouvelles annotations ont été introduites pour mieux exprimer l'intention des API en Objective-C (et pour assurer un meilleur support Swift bien sûr). Ces annotations étaient bien sûr nonnull, nullableet null_unspecified.

Mais avec Xcode 7, de nombreux avertissements apparaissent tels que:

Le pointeur n'a pas de spécificateur de type de nullabilité (_Nonnull, _Nullable ou _Null_unspecified).

En plus de cela, Apple utilise un autre type de spécificateurs de nullabilité, marquant leur code C ( source ):

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

Donc, pour résumer, nous avons maintenant ces 3 annotations de nullabilité différentes:

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

Même si je sais pourquoi et où utiliser quelle annotation, je ne sais pas trop quel type d'annotations dois-je utiliser, où et pourquoi. Voici ce que je pourrais comprendre:

  • Pour les propriétés que je devrais utiliser nonnull, nullable, null_unspecified.
  • Pour les paramètres de la méthode que je devrais utiliser nonnull, nullable, null_unspecified.
  • Pour les méthodes C je devrais utiliser __nonnull, __nullable, __null_unspecified.
  • Pour d' autres cas, comme doubles pointeurs que je devrais utiliser _Nonnull, _Nullable, _Null_unspecified.

Mais je ne comprends toujours pas pourquoi nous avons tant d'annotations qui font essentiellement la même chose.

Ma question est donc:

Quelle est la différence exacte entre ces annotations, comment les placer correctement et pourquoi?

Legoless
la source
3
J'ai lu cet article, mais il n'explique pas la différence et pourquoi nous avons 3 types d'annotations différents maintenant et je veux vraiment comprendre pourquoi ils ont continué à ajouter le troisième type.
Legoless
2
Cela n'aide vraiment pas @ Cy-4AH et vous le savez. :)
Legoless
@Legoless, êtes-vous sûr de l'avoir lu attentivement? il explique précisément où et comment vous devez les utiliser, quelles sont les portées auditées, quand vous pouvez utiliser l'autre pour une meilleure lisibilité, des raisons de compatibilité, etc, etc ... vous ne savez peut-être pas ce que vous aimez vraiment demander, mais le La réponse est clairement sous le lien. c'est peut-être juste moi, mais je ne pense pas qu'une explication supplémentaire serait nécessaire pour expliquer leur but, copier et coller cette explication simple ici comme réponse ici serait vraiment gênant, je suppose. :(
holex
2
Cela clarifie certaines parties, mais non, je ne comprends toujours pas pourquoi nous n'avons pas que les premières annotations. Cela explique seulement pourquoi ils sont passés de __nullable à _Nullable, mais pas pourquoi avons-nous même besoin de _Nullable, si nous avons nullable. Et cela n'explique pas non plus pourquoi Apple utilise toujours __nullable dans son propre code.
Legoless

Réponses:

152

De la clang documentation :

Les qualificatifs de nullabilité (type) expriment si une valeur d'un type de pointeur donné peut être null (le _Nullablequalificatif), n'a pas de signification définie pour null (le _Nonnullqualificatif), ou pour lequel le but de null n'est pas clair (le _Null_unspecifiedqualificatif) . Étant donné que les qualificatifs de nullabilité sont exprimés dans le système de types, ils sont plus généraux que les attributs nonnullet returns_nonnull, ce qui permet d'exprimer (par exemple) un pointeur de type nullable vers un tableau de pointeurs non nuls. Les qualificatifs de nullité sont écrits à droite du pointeur auquel ils s'appliquent.

, et

En Objective-C, il existe une orthographe alternative pour les qualificatifs de nullabilité qui peuvent être utilisés dans les méthodes et propriétés Objective-C à l'aide de mots-clés contextuels non soulignés

Donc, pour les retours de méthode et les paramètres, vous pouvez utiliser les versions à double soulignement __nonnull/ __nullable/ __null_unspecifiedau lieu des versions à simple soulignement ou à la place des versions non soulignées. La différence est que les simples et doubles soulignés doivent être placés après la définition de type, tandis que les non soulignés doivent être placés avant la définition de type.

Ainsi, les déclarations suivantes sont équivalentes et correctes:

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

Pour les paramètres:

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

Pour les propriétés:

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

Les choses se compliquent cependant lorsque des doubles pointeurs ou des blocs retournant quelque chose de différent de void sont impliqués, car les non-soulignés ne sont pas autorisés ici:

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

Similaire aux méthodes qui acceptent des blocs comme paramètres, veuillez noter que le qualificatif nonnull/ nullables'applique au bloc, et non à son type de retour, donc les éléments suivants sont équivalents:

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

Si le bloc a une valeur de retour, vous êtes forcé dans l'une des versions de soulignement:

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

En conclusion, vous pouvez utiliser l'un ou l'autre, tant que le compilateur peut déterminer l'élément auquel attribuer le qualificatif.

Cristik
la source
2
Il semble que les versions soulignées peuvent être utilisées partout, donc je suppose que je les utiliserai systématiquement, plutôt que d'utiliser des versions soulignées à certains endroits et celles sans soulignement à d'autres. Correct?
Vaddadi Kartick
@KartickVaddadi oui, c'est exact, vous pouvez systématiquement utiliser soit la version simple soulignée, soit la version double soulignée.
Cristik
_Null_unspecifieddans Swift, cela se traduit par facultatif? non facultatif ou quoi?
Honey
1
@Honey _Null_unspecified est importé dans Swift comme implicitement non emballé facultatif
Cristik
1
@Cristik aha. Je suppose que c'est sa valeur par défaut ... parce que quand je n'ai pas spécifié, je devenais implicitement non emballé en option ...
Honey
28

Sur le blog Swift :

Cette fonctionnalité a été publiée pour la première fois dans Xcode 6.3 avec les mots-clés __nullable et __nonnull. En raison de conflits potentiels avec des bibliothèques tierces, nous les avons modifiés dans Xcode 7 en _Nullable et _Nonnull que vous voyez ici. Cependant, pour la compatibilité avec Xcode 6.3, nous avons prédéfini des macros __nullable et __nonnull pour étendre les nouveaux noms.

Ben Thomas
la source
4
En un mot, les versions simples et doubles soulignées sont identiques.
Vaddadi Kartick
4
Également documenté dans les notes de publication de Xcode 7.0: «Les qualificatifs de nullabilité à double soulignement (__nullable, __nonnull et __null_unspecified) ont été renommés pour utiliser un seul trait de soulignement avec une majuscule: _Nullable, _Nonnull et _Null_unspecified, respectivement). Le compilateur prédéfinit les macros mappage des anciens noms doubles non spécifiés aux nouveaux noms pour la compatibilité des sources. (21530726) "
Cosyn
25

J'ai vraiment aimé cet article , donc je montre simplement ce que l'auteur a écrit: https://swiftunboxed.com/interop/objc-nullability-annotations/

  • null_unspecified:ponts vers un Swift implicitement déroulé en option. C'est la valeur par défaut .
  • nonnull: la valeur ne sera pas nulle; ponts vers une référence régulière.
  • nullable: la valeur peut être nulle; ponts à une option.
  • null_resettable: la valeur ne peut jamais être nulle lors de la lecture, mais vous pouvez la définir sur nil pour la réinitialiser. S'applique uniquement aux propriétés.

Les notations ci-dessus, diffèrent alors si vous les utilisez dans le contexte de propriétés ou de fonctions / variables:

Notation des pointeurs et des propriétés

L'auteur de l'article a également fourni un bel exemple:

// 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
la source
12

Très pratique est

NS_ASSUME_NONNULL_BEGIN 

et en terminant avec

NS_ASSUME_NONNULL_END 

Cela annulera le besoin du niveau de code 'nullibis' :-) car il est en quelque sorte logique de supposer que tout est non nul (ou nonnullou _nonnullou __nonnull) sauf indication contraire.

Malheureusement, il y a aussi des exceptions à cela ...

  • typedefs ne sont pas supposés être __nonnull(note, nonnullne semble pas fonctionner, faut utiliser son demi-frère moche)
  • id *a besoin d'un nullibi explicite mais wow le péché-tax ( _Nullable id * _Nonnull<- devinez ce que cela signifie ...)
  • NSError ** est toujours supposé nullable

Donc, à l'exception des exceptions et des mots-clés incohérents suscitant la même fonctionnalité, peut-être que l'approche consiste à utiliser les versions laides __nonnull/ __nullable/ __null_unspecifiedet à échanger lorsque le complicateur se plaint ...? C'est peut-être pourquoi ils existent dans les en-têtes Apple?

Fait intéressant, quelque chose l'a mis dans mon code ... J'ai horreur des soulignements dans le code (type de style Apple C ++ de la vieille école) donc je suis absolument sûr que je ne les ai pas tapés mais ils sont apparus (un exemple parmi plusieurs):

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

Et encore plus intéressant, là où il a inséré le __nullable est faux ... (eek @!)

J'aimerais vraiment pouvoir utiliser la version non soulignée, mais apparemment, cela ne fonctionne pas avec le compilateur car cela est signalé comme une erreur:

typedef void ( ^ DidReceiveChallengeBlock ) ( NSURLSessionAuthChallengeDisposition disposition,
                                          NSURLCredential * nonnull  credential );
William Cerniuk
la source
2
Le non-tiret bas ne peut être utilisé que directement après une parenthèse ouvrante, c'est-à-dire (non nul ... Juste pour rendre la vie plus intéressante, j'en suis sûr.
Elise van Looij
Comment rendre un id <> non nul? J'ai l'impression que cette réponse contient beaucoup de connaissances mais elle manque de clarté.
fizzybear
1
@fizzybear, certes, j'adopte généralement l'approche opposée. Les pointeurs sont mon ami et je n'ai pas eu de problèmes de pointeurs "nuls" / "nil" depuis le début des années 90. J'aimerais pouvoir faire disparaître toute la chose nullable / nilable. Mais au fait, la vraie réponse se trouve dans les 6 premières lignes de la réponse. Mais à propos de votre question: pas quelque chose que je ferais (pas de critique) donc je ne sais pas.
William Cerniuk