Unterschied zwischen nullable, __nullable und _Nullable in Objective-C

154

Mit Xcode 6.3 wurden neue Anmerkungen eingeführt, um die Absicht von APIs in Objective-C besser auszudrücken (und natürlich um eine bessere Swift-Unterstützung zu gewährleisten). Diese Anmerkungen waren natürlich nonnull, nullableund null_unspecified.

Bei Xcode 7 werden jedoch viele Warnungen angezeigt, z.

Dem Zeiger fehlt ein Nullfähigkeitstyp-Bezeichner (_Nonnull, _Nullable oder _Null_unspecified).

Darüber hinaus verwendet Apple einen anderen Typ von Nullabilitätsspezifizierern, die ihren C-Code ( Quelle ) markieren :

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

Zusammenfassend haben wir nun diese 3 verschiedenen Annotationen zur Nullbarkeit:

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

Obwohl ich weiß, warum und wo ich welche Annotation verwenden soll, bin ich etwas verwirrt darüber, welche Art von Annotationen ich wo und warum verwenden soll. Folgendes könnte ich sammeln:

  • Für Eigenschaften sollte ich verwenden nonnull, nullable, null_unspecified.
  • Für Methodenparameter sollte ich nonnull, nullable, null_unspecified.
  • Für C Methoden , die ich verwenden sollte __nonnull, __nullable, __null_unspecified.
  • In anderen Fällen, wie zum Beispiel Doppelzeiger soll ich verwenden _Nonnull, _Nullable, _Null_unspecified.

Aber ich bin immer noch verwirrt darüber, warum wir so viele Anmerkungen haben, die im Grunde das Gleiche tun.

Meine Frage lautet also:

Was ist der genaue Unterschied zwischen diesen Anmerkungen, wie werden sie richtig platziert und warum?

Legoless
quelle
3
Ich habe diesen Beitrag gelesen, aber er erklärt nicht den Unterschied und warum wir jetzt drei verschiedene Arten von Anmerkungen haben, und ich möchte wirklich verstehen, warum sie den dritten Typ hinzugefügt haben.
Legoless
2
Dies hilft @ Cy-4AH wirklich nicht und Sie wissen es. :)
Legoless
@Legoless, bist du sicher, dass du es sorgfältig gelesen hast? Es wird genau erklärt, wo und wie Sie sie verwenden sollten, was die geprüften Bereiche sind, wann Sie den anderen für eine bessere Lesbarkeit, Kompatibilitätsgründe usw. verwenden können. Sie wissen möglicherweise nicht, was Sie wirklich fragen möchten, aber die Antwort ist klar unter dem Link. Vielleicht bin ich es nur, aber ich glaube nicht, dass eine weitere Erklärung notwendig wäre, um ihren Zweck zu erklären. Das Kopieren und Einfügen dieser einfachen Erklärung als Antwort hier wäre wirklich umständlich, denke ich. :(
holex
2
Es klärt zwar bestimmte Teile auf, aber nein, ich verstehe immer noch nicht, warum wir nicht nur die ersten Anmerkungen haben. Es erklärt nur, warum sie von __nullable zu _Nullable gewechselt sind, aber nicht, warum wir _Nullable überhaupt brauchen, wenn wir nullable haben. Und es erklärt auch nicht, warum Apple __nullable immer noch in seinem eigenen Code verwendet.
Legoless

Antworten:

152

Aus der clang Dokumentation :

Die Qualifizierer für die Nullfähigkeit (Typ) drücken aus, ob ein Wert eines bestimmten Zeigertyps null (das _NullableQualifikationsmerkmal) sein kann, keine definierte Bedeutung für null (das _NonnullQualifikationsmerkmal) hat oder für den der Zweck von null unklar ist (das _Null_unspecifiedQualifikationsmerkmal). . Da Nullabilitätsqualifizierer innerhalb des Typsystems ausgedrückt werden, sind sie allgemeiner als die Attribute nonnullund returns_nonnull, sodass man beispielsweise einen nullbaren Zeiger auf ein Array von Nicht-Null-Zeigern ausdrücken kann. Nullabilitätsqualifizierer werden rechts von dem Zeiger geschrieben, für den sie gelten.

, und

In Objective-C gibt es eine alternative Schreibweise für die Nullabilitätsqualifizierer, die in Objective-C-Methoden und -Eigenschaften unter Verwendung kontextsensitiver, nicht unterstrichener Schlüsselwörter verwendet werden können

Also für Methode zurückgibt und Parameter , die Sie die die Doppelunterstrichene Möglichkeiten nutzen __nonnull/ __nullable/ __null_unspecifiedstatt entweder die Einzelunterstrichene diejenigen, oder anstelle der nicht-unterstrichene diejenigen. Der Unterschied besteht darin, dass die einfach und doppelt unterstrichenen nach der Typdefinition platziert werden müssen, während die nicht unterstrichenen vor der Typdefinition stehen müssen.

Somit sind die folgenden Erklärungen gleichwertig und richtig:

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

Für Parameter:

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

Für Eigenschaften:

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

Die Dinge werden jedoch komplizierter, wenn es sich um Doppelzeiger oder Blöcke handelt, die etwas anderes als void zurückgeben, da die nicht unterstrichenen hier nicht zulässig sind:

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

Ähnlich wie bei Methoden, die Blöcke als Parameter akzeptieren, beachten Sie bitte, dass das Qualifikationsmerkmal nonnull/ nullablefür den Block und nicht für seinen Rückgabetyp gilt. Daher sind die folgenden äquivalent:

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

Wenn der Block einen Rückgabewert hat, werden Sie in eine der Unterstrichversionen gezwungen:

- (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 Fazit können Sie beide verwenden, solange der Compiler das Element bestimmen kann, dem das Qualifikationsmerkmal zugewiesen werden soll.

Cristik
quelle
2
Es scheint, dass die Unterstrichversionen überall verwendet werden können, daher werde ich sie wahrscheinlich konsequent verwenden, anstatt an einigen Stellen unterstrichene Versionen und an anderen Stellen ohne Unterstrich zu verwenden. Richtig?
Vaddadi Kartick
@KartickVaddadi Ja, richtig, Sie können konsistent entweder die einfach unterstrichene oder die doppelt unterstrichene Version verwenden.
Cristik
_Null_unspecifiedin Swift bedeutet dies optional? nicht optional oder was?
Honig
1
@Honey _Null_unspecified wird in Swift als implizit unverpackt optional importiert
Cristik
1
@ Cristik aha. Ich denke, das ist der Standardwert ... denn als ich nicht spezifizierte, wurde ich implizit optional ausgepackt ...
Honey
28

Aus dem Swift-Blog :

Diese Funktion wurde erstmals in Xcode 6.3 mit den Schlüsselwörtern __nullable und __nonnull veröffentlicht. Aufgrund möglicher Konflikte mit Bibliotheken von Drittanbietern haben wir sie in Xcode 7 in _Nullable und _Nonnull geändert, die Sie hier sehen. Aus Gründen der Kompatibilität mit Xcode 6.3 haben wir jedoch die Makros __nullable und __nonnull vordefiniert, um sie auf die neuen Namen zu erweitern.

Ben Thomas
quelle
4
Kurz gesagt, die einfach und doppelt unterstrichenen Versionen sind identisch.
Vaddadi Kartick
4
Ebenfalls in den Versionshinweisen zu Xcode 7.0 dokumentiert: "Die doppelt unterstrichenen Nullabilitätsqualifizierer (__nullable, __nonnull und __null_unspecified) wurden umbenannt, um einen einzelnen Unterstrich mit einem Großbuchstaben zu verwenden: _Nullable, _Nonnull bzw. _Null_unspecified. Der Compiler definiert Makros vor." Zuordnung von den alten doppelt nicht spezifizierten Namen zu den neuen Namen aus Gründen der Quellkompatibilität. (21530726) "
Cosyn
25

Dieser Artikel hat mir sehr gut gefallen , daher zeige ich nur, was der Autor geschrieben hat: https://swiftunboxed.com/interop/objc-nullability-annotations/

  • null_unspecified:Brücken zu einem implizit entpackten Swift optional. Dies ist die Standardeinstellung .
  • nonnull: der Wert wird nicht Null sein; Brücken zu einer regelmäßigen Referenz.
  • nullable: der Wert kann Null sein; Brücken zu einem optionalen.
  • null_resettable: Der Wert kann beim Lesen niemals Null sein, aber Sie können ihn auf Null setzen, um ihn zurückzusetzen. Gilt nur für Eigenschaften.

Die obigen Notationen unterscheiden sich dann, ob Sie sie im Kontext von Eigenschaften oder Funktionen / Variablen verwenden:

Notation Zeiger vs. Eigenschaften

Der Autor des Artikels lieferte auch ein schönes Beispiel:

// 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
quelle
12

Sehr praktisch ist

NS_ASSUME_NONNULL_BEGIN 

und schließen mit

NS_ASSUME_NONNULL_END 

Dies macht die Notwendigkeit der Codeebene 'nullibis' zunichte :-), da es sinnvoll ist anzunehmen, dass alles nicht null (oder nonnulloder _nonnulloder __nonnull) ist, sofern nicht anders angegeben.

Leider gibt es auch hier Ausnahmen ...

  • typedefs werden nicht angenommen __nonnull(beachten Sie, nonnullscheint nicht zu funktionieren, muss es hässlichen Halbbruder verwenden)
  • id *braucht ein explizites Nullibi, aber wow die Sündensteuer ( _Nullable id * _Nonnull<- rate mal was das bedeutet ...)
  • NSError ** wird immer als nullbar angenommen

So mit den Ausnahmen von den Ausnahmen und die inkonsistenten Schlüsselwörter die gleiche Funktionalität entlocken, vielleicht der Ansatz ist es, die hässlichen Versionen verwenden __nonnull/ __nullable/__null_unspecified und Swap , wenn die complier ... beschwert? Vielleicht existieren sie deshalb in den Apple-Headern?

Interessanterweise hat es etwas in meinen Code eingefügt ... Ich verabscheue Unterstriche im Code (Apple C ++ - Typ der alten Schule), daher bin ich mir absolut sicher, dass ich diese nicht eingegeben habe, aber sie erschienen sind (ein Beispiel von mehreren):

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

Und noch interessanter ist, wo das __nullable eingefügt wurde, ist falsch ... (eek @!)

Ich wünschte wirklich, ich könnte nur die Nicht-Unterstrich-Version verwenden, aber anscheinend funktioniert das nicht mit dem Compiler, da dies als Fehler gekennzeichnet ist:

typedef void ( ^ DidReceiveChallengeBlock ) ( NSURLSessionAuthChallengeDisposition disposition,
                                          NSURLCredential * nonnull  credential );
William Cerniuk
quelle
2
Nicht-Unterstrich kann nur direkt nach einer öffnenden Klammer verwendet werden, dh (nicht null ... Nur um das Leben interessanter zu machen, bin ich sicher.
Elise van Looij
Wie mache ich eine ID <> nichtig? Ich habe das Gefühl, dass diese Antwort viel Wissen enthält, aber es fehlt ihr an Klarheit.
Fizzybear
1
@fizzybear zugegebenermaßen gehe ich normalerweise umgekehrt vor. Zeiger sind mein Freund und ich hatte seit Anfang der 90er Jahre keine "null" / "null" Zeigerprobleme mehr. Ich wünschte, ich könnte das ganze nullable / nilable Ding einfach verschwinden lassen. Die eigentliche Antwort befindet sich jedoch in den ersten 6 Zeilen der Antwort. Aber zu Ihrer Frage: Ich würde nichts tun (keine Kritik), also weiß ich es einfach nicht.
William Cerniuk