Diferencia entre nullable, __nullable y _Nullable en Objective-C

154

Con Xcode 6.3, se introdujeron nuevas anotaciones para expresar mejor la intención de las API en Objective-C (y para garantizar un mejor soporte de Swift, por supuesto). Esas anotaciones eran, por supuesto nonnull, nullabley null_unspecified.

Pero con Xcode 7, aparecen muchas advertencias como:

Al puntero le falta un especificador de tipo de nulabilidad (_Nnnull, _Nullable o _Null_unspecified).

Además de eso, Apple usa otro tipo de especificadores de nulabilidad, marcando su código C ( fuente ):

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

En resumen, ahora tenemos estas 3 anotaciones de nulabilidad diferentes:

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

Aunque sé por qué y dónde usar qué anotación, me confundo un poco qué tipo de anotaciones debo usar, dónde y por qué. Esto es lo que podría reunir:

  • Para las propiedades que debe utilizar nonnull, nullable,null_unspecified .
  • Para los parámetros del método que debe utilizar nonnull, nullable,null_unspecified .
  • Para los métodos de C I deberán utilizar __nonnull, __nullable,__null_unspecified .
  • Para otros casos, como punteros dobles que debería utilizar _Nonnull, _Nullable, _Null_unspecified.

Pero todavía estoy confundido sobre por qué tenemos tantas anotaciones que básicamente hacen lo mismo.

Entonces mi pregunta es:

¿Cuál es la diferencia exacta entre esas anotaciones, cómo colocarlas correctamente y por qué?

Sin lego
fuente
3
He leído esa publicación, pero no explica la diferencia y por qué tenemos 3 tipos diferentes de anotaciones ahora y realmente quiero entender por qué continuaron agregando el tercer tipo.
Legoless
2
Esto realmente no ayuda a @ Cy-4AH y lo sabes. :)
Legoless
@Legoless, ¿estás seguro de que lo leíste atentamente? explica con precisión dónde y cómo debe usarlos, cuáles son los ámbitos auditados, cuándo puede usar el otro para una mejor legibilidad, razones de compatibilidad, etc., etc. Puede que no sepa lo que realmente le gusta preguntar, pero el La respuesta está claramente debajo del enlace. tal vez sea solo yo, pero no creo que sea necesaria ninguna explicación adicional para explicar su propósito, copiar y pegar esa explicación simple aquí como respuesta aquí sería realmente incómodo, supongo. :(
holex
2
Aclara ciertas partes, pero no, todavía no entiendo por qué no solo tenemos las primeras anotaciones. Solo explica por qué pasaron de __nullable a _Nullable, pero no por qué necesitamos _Nullable, si tenemos nullable. Y tampoco explica por qué Apple todavía usa __nullable en su propio código.
Legoless

Respuestas:

152

De la clang documentación :

Los calificadores de nulabilidad (tipo) expresan si un valor de un tipo de puntero dado puede ser nulo (el _Nullablecalificador), si no tiene un significado definido para nulo (el _Nonnullcalificador) o si el propósito de nulo no está claro (el _Null_unspecifiedcalificador) . Debido a que los calificadores de nulabilidad se expresan dentro del sistema de tipos, son más generales que los atributos nonnully returns_nonnull, lo que permite expresar (por ejemplo) un puntero anulable a una matriz de punteros no nulos. Los calificadores de nulabilidad se escriben a la derecha del puntero al que se aplican.

y

En Objective-C, hay una ortografía alternativa para los calificadores de nulabilidad que se pueden usar en los métodos y propiedades de Objective-C usando palabras clave sensibles al contexto y no subrayadas

Por lo tanto, para las devoluciones de métodos y los parámetros, puede usar las versiones con doble subrayado __nonnull/ __nullable/ en __null_unspecifiedlugar de las versiones con subrayado simple o en lugar de las que no están subrayadas. La diferencia es que los subrayados simples y dobles deben colocarse después de la definición del tipo, mientras que los que no están subrayados deben colocarse antes de la definición del tipo.

Por lo tanto, las siguientes declaraciones son equivalentes y son correctas:

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

Para parámetros:

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

Para propiedades:

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

Sin embargo, las cosas se complican cuando se involucran dobles punteros o bloques que devuelven algo diferente a vacío, ya que los que no están subrayados no están permitidos aquí:

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

Similar a los métodos que aceptan bloques como parámetros, tenga en cuenta que el calificador nonnull/ nullablese aplica al bloque y no a su tipo de retorno, por lo tanto, los siguientes son equivalentes:

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

Si el bloque tiene un valor de retorno, entonces estás obligado a una de las versiones de subrayado:

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

Como conclusión, puede usar cualquiera de ellos, siempre que el compilador pueda determinar el elemento al que asignarle el calificador.

Cristik
fuente
2
Parece que las versiones de guiones bajos se pueden usar en todas partes, así que supongo que las usaré constantemente, en lugar de usar versiones subrayadas en algunos lugares y sin guiones bajos en otros. ¿Correcto?
Vaddadi Kartick
@KartickVaddadi sí, correcto, puede usar consistentemente las versiones de subrayado simple o doble subrayado.
Cristik
_Null_unspecifieden Swift esto se traduce en opcional? no opcional o qué?
Miel el
1
@Honey _Null_unspecified se importa en Swift como Opcionalmente sin envolver opcional
Cristik
1
@Cristik aha. Supongo que es su valor por defecto ... porque cuando no especifico que estaba recibiendo Implícitamente sin envolver opcional ...
Miel
28

Del blog de Swift :

Esta característica se lanzó por primera vez en Xcode 6.3 con las palabras clave __nullable y __nonnull. Debido a posibles conflictos con bibliotecas de terceros, las hemos cambiado en Xcode 7 a _Nullable y _Nnnull que ves aquí. Sin embargo, para la compatibilidad con Xcode 6.3, hemos predefinido las macros __nullable y __nonnull para expandir a los nuevos nombres.

Ben Thomas
fuente
44
En pocas palabras, las versiones de subrayado simple y doble son idénticas.
Vaddadi Kartick
44
También documentado en las notas de lanzamiento de Xcode 7.0: "Los calificadores de nulabilidad con doble subrayado (__nullable, __nonnull y __null_unspecified) se han renombrado para usar un solo guión bajo con una letra mayúscula: _Nullable, _Nonnull y _Null_unspecified, respectivamente). El compilador predefine macros mapeo de los antiguos nombres dobles no especificados a los nuevos nombres para compatibilidad de fuente. (21530726) "
Cosyn
25

Realmente me gustó este artículo , así que simplemente estoy mostrando lo que escribió el autor: https://swiftunboxed.com/interop/objc-nullability-annotations/

  • null_unspecified:puentes a un Swift implícitamente desenvuelto opcional. Este es el valor predeterminado .
  • nonnull: el valor no será nulo; puentes a una referencia regular.
  • nullable: el valor puede ser nulo; puentes a un opcional.
  • null_resettable: el valor nunca puede ser nulo cuando se lee, pero puede establecerlo en nulo para restablecerlo. Solo se aplica a propiedades.

Las anotaciones anteriores, luego difieren si las usa en el contexto de propiedades o funciones / variables:

Punteros vs.Notación de propiedades

El autor del artículo también proporcionó un buen ejemplo:

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

Muy útil es

NS_ASSUME_NONNULL_BEGIN 

y cerrando con

NS_ASSUME_NONNULL_END 

Esto anulará la necesidad del nivel de código 'nullibis' :-) ya que tiene sentido suponer que todo no es nulo (o nonnullo _nonnullo__nonnull ) a menos que se indique lo contrario.

Lamentablemente, hay excepciones a esto también ...

  • typedefNo se supone que s __nonnull(nota, nonnullno parece funcionar, tiene que usar su medio hermano feo)
  • id *necesita un nullibi explícito pero wow the sin-tax ( _Nullable id * _Nonnull<- adivina lo que eso significa ...)
  • NSError ** siempre se supone nulable

Entonces, con las excepciones a las excepciones y las palabras clave inconsistentes que generan la misma funcionalidad, quizás el enfoque sea usar las versiones feas __nonnull/ __nullable/__null_unspecified e intercambiar cuando el cumplidor se queja ...? ¿Quizás es por eso que existen en los encabezados de Apple?

Curiosamente, algo lo puso en mi código ... Aborrezco los guiones bajos en el código (tipo de la vieja escuela Apple C ++), así que estoy absolutamente seguro de que no los escribí pero aparecieron (un ejemplo de varios):

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

Y aún más interesante, donde insertó el __nullable está mal ... (¡eek @!)

Realmente desearía poder usar la versión que no es de subrayado, pero aparentemente eso no vuela con el compilador ya que esto se marca como un error:

typedef void ( ^ DidReceiveChallengeBlock ) ( NSURLSessionAuthChallengeDisposition disposition,
                                          NSURLCredential * nonnull  credential );
William Cerniuk
fuente
2
El guión bajo solo se puede usar directamente después de un paréntesis de apertura, es decir (no nulo ... Solo para hacer la vida más interesante, estoy seguro.
Elise van Looij
¿Cómo hago un id <> no nulo? Siento que esta respuesta contiene mucho conocimiento pero carece de claridad.
fizzybear
1
@fizzybear es cierto que normalmente adopto el enfoque opuesto. Los punteros son mis amigos y no he tenido problemas con los punteros "nulos" / "nulos" desde principios de los 90. Desearía poder hacer que todo lo que se puede anular / anular desaparezca. Pero hasta el punto, la respuesta real está en las primeras 6 líneas de la respuesta. Pero sobre su pregunta: no es algo que haría (sin críticas), así que simplemente no lo sé.
William Cerniuk