web-dev-qa-db-fra.com

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

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

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

Il manque au spécificateur un spécificateur de type nullable (_Nonnull, _Nullable ou _Null_unspecified).

De plus, 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 suis un peu confus quant au type d'annotations que je devrais utiliser, où et pourquoi. Voici ce que je pourrais rassembler:

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

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

Donc ma question est:

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

138
Legoless

De la clangdocumentation :

Les qualificatifs nullability (type) indiquent si une valeur d’un type de pointeur donné peut être nulle (qualificateur _Nullable), si elle n’a pas de signification définie pour null (qualificateur _Nonnull]) ou pour laquelle l'objectif de null n'est pas clair (qualificatif _Null_unspecified). Comme les qualificateurs de nullabilité sont exprimés dans le système de types, ils sont plus généraux que les attributs nonnull et returns_nonnull, ce qui permet d’exprimer (par exemple) un pointeur nullable vers un tableau de pointeurs non null. Les qualificateurs de nullabilité sont écrits à la droite du pointeur auquel ils s'appliquent.

, et

En Objective-C, il existe une autre orthographe pour les qualificateurs de nullabilité qui peuvent être utilisés dans les méthodes et propriétés Objective-C à l'aide de mots clés sensibles au contexte, non soulignés.

Ainsi, pour les retours de méthode et les paramètres, vous pouvez utiliser les versions à double soulignement __nonnull/__nullable/__null_unspecified au lieu des versions à soulignement simple ou à la place des versions non soulignées. La différence est que les symboles simples et doubles soulignés doivent être placés après la définition du type, tandis que ceux non soulignés doivent être placés avant la définition du 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 renvoyant quelque chose de différent du vide 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

Semblable aux méthodes qui acceptent les blocs en tant que paramètres, veuillez noter que le qualificateur nonnull/nullable s'applique au bloc et non à son type de retour. Par conséquent, 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, à condition que le compilateur puisse déterminer l'élément auquel attribuer le qualificatif.

137
Cristik

De le Swift blog :

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ées dans Xcode 7 en _Nullable et _Nonnull que vous voyez ici. Toutefois, pour assurer la compatibilité avec Xcode 6.3, nous avons prédéfini les macros __nullable et __nonnull à développer avec les nouveaux noms.

25
Ben Thomas

J'ai vraiment aimé cet article, je ne fais donc que montrer ce que l'auteur a écrit: https: // swiftunboxed .com/interop/objc-nullable-annotations /

  • null_unspecified: se connecte à un Swift implicitement non déballé, facultatif. C'est le défaut .
  • nonnull: la valeur ne sera pas nulle; des ponts vers une référence régulière.
  • nullable: la valeur peut être nulle; ponts à un optionnel.
  • null_resettable: la valeur ne peut jamais être nulle à 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, que vous les utilisiez dans le contexte de propriétés ou de fonctions/variables:

Pointers vs. Properties notation

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;
23
kgaidis

Très pratique est

NS_ASSUME_NONNULL_BEGIN 

et fermer avec

NS_ASSUME_NONNULL_END 

Cela annulera la nécessité du niveau de code 'nullibis' :-) car il est logique de supposer que tout n'est pas nul (ou nonnull ou _nonnull ou __nonnull) sauf indication contraire.

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

  • typedefs ne sont pas supposés être __nonnull (note, nonnull ne semble pas fonctionner, vous devez utiliser son vilain demi-frère)
  • id * nécessite un nullibi explicite, mais wow le péché-taxe (_Nullable id * _Nonnull <- devinez ce que cela signifie ...)
  • NSError ** est toujours supposé Nullable

Donc, avec les exceptions aux exceptions et les mots-clés incohérents induisant la même fonctionnalité, l’approche consiste peut-être à utiliser les versions laides __nonnull/__nullable/__null_unspecified et à échanger lorsque le compliant se plaint. . C’est peut-être pour cette raison qu’ils existent dans les en-têtes Apple?

Il est intéressant de noter que quelque chose a mis cela dans mon code ... Je déteste les caractères de soulignement (old school Apple du type C++) et je suis absolument sûr de ne pas les avoir tapés mais ils sont apparus (un exemple parmi plusieurs) :

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

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

J'aurais vraiment aimé pouvoir utiliser la version sans trait de soulignement, mais apparemment, cela ne fonctionne pas avec le compilateur, car cela est signalé comme une erreur:

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