Met Xcode 6.3 zijn er nieuwe annotaties geïntroduceerd om de bedoeling van API’s beter tot uitdrukking te brengen in Objective-C(en om natuurlijk voor betere Swift-ondersteuning te zorgen). Die annotaties waren natuurlijk nonnull
, nullable
en null_unspecified
.
Maar met Xcode 7 verschijnen er veel waarschuwingen zoals:
Aanwijzer mist een specificatie van het type nullabiliteit (_Nonnull, _Nullable of _Null_unspecified).
Daarnaast gebruikt Apple een ander type nullability-specificaties, die hun C-code markeren (bron):
CFArrayRef __nonnull CFArrayCreate(
CFAllocatorRef __nullable allocator, const void * __nonnull * __nullable values, CFIndex numValues, const CFArrayCallBacks * __nullable callBacks);
Dus, om samen te vatten, we hebben nu deze 3 verschillende nullability-annotaties:
nonnull
,nullable
,null_unspecified
_nonnull
,_Nullable
,_Null_unspecified
__nonnull
,__nullable
,__null_unspecified
Hoewel ik weet waarom en waar ik welke annotatie moet gebruiken, raak ik een beetje in de war over welk type annotatie ik moet gebruiken, waar en waarom. Dit is wat ik kon verzamelen:
- Voor eigenschappen moet ik
nonnull
,nullable
,null_unspecified
gebruiken. - Voor methodeparameters moet ik
nonnull
,nullable
,null_unspecified
gebruiken. - Voor C-methoden moet ik
__nonnull
,__nullable
,__null_unspecified
gebruiken. - Voor andere gevallen, zoals dubbele verwijzingen, moet ik
_nonnull
,_Nullable
,_Null_unspecified
gebruiken.
Maar ik weet nog steeds niet waarom we zoveel annotaties hebben die in feite hetzelfde doen.
Dus mijn vraag is:
Wat is het exacte verschil tussen die annotaties, hoe je ze correct plaatst en waarom?
Antwoord 1, autoriteit 100%
Van de clang
documentatie:
De kwalificaties voor nullabiliteit (type) geven aan of een waarde van een bepaald type aanwijzer null kan zijn (de kwalificatie
_Nullable
), geen gedefinieerde betekenis heeft voor null (de_nonnull
kwalificatie), of waarvoor het doel van null onduidelijk is (de_Null_unspecified
kwalificatie). Omdat nullability-kwalificaties worden uitgedrukt binnen het typesysteem, zijn ze algemener dan de attributennonnull
enreturns_nonnull
, waardoor men (bijvoorbeeld) een nullable pointer kan uitdrukken naar een array van niet-null-aanwijzers. Nullability-kwalificaties worden rechts van de aanwijzer waarop ze van toepassing zijn geschreven.
, en
In Objective-C is er een alternatieve spelling voor de nullability-kwalificaties die kunnen worden gebruikt in Objective-C-methoden en -eigenschappen met behulp van contextgevoelige, niet-onderstreepte trefwoorden
Dus voor methode-returns en parameters kun je de . gebruiken
de dubbel onderstreepte versies __nonnull
/__nullable
/__null_unspecified
in plaats van de enkel onderstreepte versies, of in plaats van de niet-onderstreepte versies. Het verschil is dat de enkele en dubbele onderstreepte tekens achter de typedefinitie moeten worden geplaatst, terwijl de niet-onderstreepte tekens vóór de typedefinitie moeten worden geplaatst.
De volgende verklaringen zijn dus equivalent en correct:
- (nullable NSNumber *)result
- (NSNumber * __nullable)result
- (NSNumber * _Nullable)result
Voor parameters:
- (void)doSomethingWithString:(nullable NSString *)str
- (void)doSomethingWithString:(NSString * _Nullable)str
- (void)doSomethingWithString:(NSString * __nullable)str
Voor eigendommen:
@property(nullable) NSNumber *status
@property NSNumber *__nullable status
@property NSNumber * _Nullable status
Het wordt echter ingewikkelder als er dubbele aanwijzers of blokken zijn die iets anders teruggeven dan ongeldig, omdat de niet-onderstrepingstekens hier niet zijn toegestaan:
- (void)compute:(NSError * _Nullable * _Nullable)error
- (void)compute:(NSError * __nullable * _Null_unspecified)error;
// and all other combinations
Vergelijkbaar met methoden die blokken als parameters accepteren, houd er rekening mee dat de kwalificatie nonnull
/nullable
van toepassing is op het blok, en niet op het retourtype, dus de volgende zijn equivalent :
- (void)executeWithCompletion:(nullable void (^)())handler
- (void)executeWithCompletion:(void (^ _Nullable)())handler
- (void)executeWithCompletion:(void (^ __nullable)())handler
Als het blok een retourwaarde heeft, wordt u gedwongen tot een van de underscore-versies:
- (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
Als conclusie kun je beide gebruiken, zolang de compiler maar kan bepalen aan welk item de kwalificatie moet worden toegewezen.
Antwoord 2, autoriteit 17%
Van het Swift-blog:
Deze functie werd voor het eerst uitgebracht in Xcode 6.3 met de trefwoorden
__nullable en __nonnull. Vanwege mogelijke conflicten met bibliotheken van derden hebben we ze in Xcode 7 gewijzigd in de _Nullable en _Nonnull
zie je hier. Voor compatibiliteit met Xcode 6.3 hebben we echter
vooraf gedefinieerde macro’s __nullable en __nonnull om uit te breiden naar de nieuwe namen.
Antwoord 3, autoriteit 17%
Van de clang-documentatie:
De kwalificaties voor nullability (type) geven aan of een waarde van een bepaald pointertype null kan zijn.
Meestal gebruikt u nonnull
en nullable
.
Hieronder staan alle beschikbare specificaties. Uit dit artikel:
null_unspecified:
Dit is de standaardinstelling.Het vormt een brug naar een impliciet uitgepakte Swift-optie.nonnull
: de waarde zal niet nul zijn. Het vormt een brug naar een reguliere Swift-referentie.nullable
: de waarde kan nul zijn. Het overbrugt naar een Swift optioneel.null_resettable
: de waarde kan bij het lezen nooit nul zijn, maar je kunt hem op nul zetten om hem te resetten. Alleen van toepassing op eigendommen.
De bovenstaande notaties verschillen of je ze gebruikt in de contextvan eigenschappen of functies/variabelen:
De auteur van het artikel gaf ook een mooi voorbeeld:
// 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;
Antwoord 4, autoriteit 8%
Heel handig is
NS_ASSUME_NONNULL_BEGIN
en afsluiten met
NS_ASSUME_NONNULL_END
Hierdoor vervalt de noodzaak van het codeniveau ‘nullibis’ 🙂 omdat het logisch is om aan te nemen dat allesniet-null is (of nonnull
of _nonnull
of __nonnull
) tenzij anders vermeld.
Helaas zijn hier ook uitzonderingen op…
typedef
s worden niet verondersteld__nonnull
te zijn (let op,nonnull
lijkt niet te werken, moet zijn lelijke halfbroer gebruiken)id *
heeft een expliciete nullibi nodig, maar wow de sin-tax (_Nullable id * _Nonnull
<- raad eens wat dat betekent…)NSError **
wordt altijd verondersteld nullable te zijn
Dus met de uitzonderingen op de uitzonderingen en de inconsistente trefwoorden die dezelfde functionaliteit oproepen, is de benadering misschien om de lelijke versies __nonnull
/ __nullable
/ __null_unspecified
en ruilen wanneer de complier klaagt… ? Misschien staan ze daarom in de Apple-headers?
Interessant genoeg heeft iets het in mijn code gestopt… Ik verafschuw onderstrepingstekens in code (old school Apple C++ style guy) dus ik weet absoluut zeker dat ik deze niet heb getypt, maar ze verschenen (een voorbeeld van meerdere):
p>
typedef void ( ^ DidReceiveChallengeBlock ) ( NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential * __nullable credential );
En nog interessanter, waar de __nullable is ingevoegd, is verkeerd… (eek@!)
Ik zou echt willen dat ik de versie zonder onderstrepingsteken kon gebruiken, maar blijkbaar werkt dat niet met de compiler omdat dit wordt gemarkeerd als een fout:
typedef void ( ^ DidReceiveChallengeBlock ) ( NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential * nonnull credential );