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:
nonnull
, nullable
, null_unspecified
.nonnull
, nullable
, null_unspecified
.__nonnull
, __nullable
, __null_unspecified
._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?
De la clang
documentation :
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 attributsnonnull
etreturns_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.
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.
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:
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;
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 ...
typedef
s 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é NullableDonc, 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 );