Diferença entre nullable, __nullable e _Nullable no Objective-C

154

Com o Xcode 6.3, foram introduzidas novas anotações para expressar melhor a intenção das APIs no Objective-C (e para garantir melhor suporte Swift, é claro). Essas anotações eram nonnull, é claro , nullablee null_unspecified.

Mas com o Xcode 7, existem muitos avisos, como:

O ponteiro está ausente em um especificador de tipo de nulidade (_Nonnull, _Nullable ou _Null_unspecified).

Além disso, a Apple usa outro tipo de especificadores de nulidade, marcando seu código C ( fonte ):

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

Então, para resumir, agora temos essas três anotações de nulidade diferentes:

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

Embora eu saiba por que e onde usar qual anotação, estou ficando um pouco confuso com o tipo de anotação que devo usar, onde e por que. Isto é o que eu pude reunir:

  • Para propriedades que eu deveria usar nonnull, nullable, null_unspecified.
  • Para os parâmetros do método devo usar nonnull, nullable, null_unspecified.
  • Para os métodos c I deve usar __nonnull, __nullable, __null_unspecified.
  • Para outros casos, como ponteiros duplos devo usar _Nonnull, _Nullable, _Null_unspecified.

Mas ainda estou confuso sobre o motivo pelo qual temos tantas anotações que basicamente fazem a mesma coisa.

Então, minha pergunta é:

Qual é a diferença exata entre essas anotações, como colocá-las corretamente e por quê?

Legoless
fonte
3
Eu li esse post, mas ele não explica a diferença e por que temos três tipos diferentes de anotações agora e eu realmente quero entender por que eles adicionaram o terceiro tipo.
Legoless
2
Isso realmente não ajuda o Cy-4AH e você sabe disso. :)
Legoless
@ Legoless, você tem certeza que leu com atenção? explica precisamente onde e como você deve usá-los, quais são os escopos auditados, quando você pode usar o outro para melhor legibilidade, motivos de compatibilidade, etc, etc ... talvez você não saiba o que realmente gosta de perguntar, mas o a resposta está claramente no link. talvez seja apenas eu, mas não acho que seja necessária outra explicação para explicar seu propósito, copiar e colar essa explicação simples aqui como resposta aqui seria realmente embaraçoso, eu acho. :(
holex 8/09/2015
2
Isso esclarece algumas partes, mas não, ainda não entendo por que não temos apenas as primeiras anotações. Ele só explica por que eles passaram de __nullable para _Nullable, mas não por que precisamos de _Nullable, se temos nulos. E também não explica por que a Apple ainda usa __nullable em seu próprio código.
Legoless

Respostas:

152

A partir da clang documentação :

Os qualificadores de nulidade (tipo) expressam se um valor de um determinado tipo de ponteiro pode ser nulo (o _Nullablequalificador), não tem um significado definido para nulo (o _Nonnullqualificador) ou para o qual o objetivo de nulo não é claro (o _Null_unspecifiedqualificador) . Como os qualificadores de nulidade são expressos no sistema de tipos, eles são mais gerais que os atributos nonnulle returns_nonnull, permitindo expressar (por exemplo) um ponteiro nulo para uma matriz de ponteiros não nulos. Qualificadores de nulidade são gravados à direita do ponteiro ao qual eles se aplicam.

e

No Objective-C, há uma ortografia alternativa para os qualificadores de nulidade que podem ser usados ​​nos métodos e propriedades do Objective-C usando palavras-chave sensíveis ao contexto e não sublinhadas

Portanto, para retornos e parâmetros de método, você pode usar as versões com sublinhado duplo __nonnull/ __nullable/ em __null_unspecifiedvez das com sublinhado único ou das não-sublinhadas. A diferença é que os sublinhados simples e duplo precisam ser colocados após a definição de tipo, enquanto os não sublinhados precisam ser colocados antes da definição de tipo.

Portanto, as seguintes declarações são equivalentes e corretas:

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

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

As coisas, no entanto, complicam quando ponteiros duplos ou blocos retornando algo diferente de nulo estão envolvidos, pois os não sublinhados não são permitidos aqui:

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

Semelhante aos métodos que aceitam blocos como parâmetros, observe que o nonnull/ nullablequalificador se aplica ao bloco, e não ao seu tipo de retorno; portanto, o seguinte é equivalente:

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

Se o bloco tiver um valor de retorno, você será forçado a uma das versões de sublinhado:

- (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 conclusão, você pode usar qualquer um deles, desde que o compilador possa determinar o item ao qual atribuir o qualificador.

Cristik
fonte
2
Parece que as versões de sublinhado podem ser usadas em todos os lugares, portanto, suponho que as usarei consistentemente, em vez de usar versões de sublinhado em alguns lugares e aquelas sem o sublinhado em outros. Corrigir?
Vaddadi Kartick
@KartickVaddadi sim, correto, você pode usar consistentemente as versões com sublinhado único ou com sublinhado duplo.
Cristik
_Null_unspecifiedno Swift isso se traduz em opcional? não opcional ou o quê?
Mel
1
@Honey _Null_unspecified é importado no Swift como Implicitly Unwrapped Opcional
Cristik
1
@Cristik aha. Eu acho que é o seu valor padrão ... porque quando eu não especificar eu estava ficando Implicitamente desembrulhado opcional ...
Mel
28

Desde o blog Swift :

Esse recurso foi lançado pela primeira vez no Xcode 6.3 com as palavras-chave __nullable e __nonnull. Devido a possíveis conflitos com bibliotecas de terceiros, as alteramos no Xcode 7 para _Nullable e _Nonnull que você vê aqui. No entanto, para compatibilidade com o Xcode 6.3, predefinimos as macros __nullable e __nonnull para expandir para os novos nomes.

Ben Thomas
fonte
4
Em poucas palavras, as versões simples e dupla sublinhadas são idênticas.
Vaddadi Kartick
4
Também documentado nas notas de versão do Xcode 7.0: "Os qualificadores de nulidade com sublinhado duplo (__nullable, __nonnull e __null_unspecified) foram renomeados para usar um único sublinhado com uma letra maiúscula: _Nullable, _Nonnull e _Null_unspecified, respectivamente). O compilador predefine macros mapeamento dos antigos nomes duplos não especificados para os novos nomes para compatibilidade de origem. (21530726) "
Cosyn
25

Gostei muito deste artigo , por isso estou apenas mostrando o que o autor escreveu: https://swiftunboxed.com/interop/objc-nullability-annotations/

  • null_unspecified:pontes para um opcional Swift implicitamente desembrulhado. Esse é o padrão .
  • nonnull: o valor não será nulo; pontes para uma referência regular.
  • nullable: o valor pode ser nulo; pontes para um opcional.
  • null_resettable: o valor nunca pode ser nulo quando lido, mas você pode configurá-lo como nulo para redefini-lo. Aplica-se apenas a propriedades.

As anotações acima diferem se você as utiliza no contexto de propriedades ou funções / variáveis:

Notação Ponteiros vs. Propriedades

O autor do artigo também forneceu um bom exemplo:

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

Muito útil é

NS_ASSUME_NONNULL_BEGIN 

e fechando com

NS_ASSUME_NONNULL_END 

Isso anulará a necessidade do nível de código 'nullibis' :-), pois meio que faz sentido supor que tudo é não nulo (ou nonnullou _nonnullou __nonnull), a menos que indicado de outra forma.

Infelizmente, também há exceções ...

  • typedefNão se supõe que s __nonnull(note, nonnullnão parece funcionar, tem que usar seu meio-irmão feio)
  • id *precisa de um nullibi explícito, mas wow o imposto do pecado ( _Nullable id * _Nonnull<- adivinhe o que isso significa ...)
  • NSError ** é sempre assumido nulo

Portanto, com as exceções às exceções e as palavras-chave inconsistentes que provocam a mesma funcionalidade, talvez a abordagem seja usar as versões feias __nonnull/ __nullable/ __null_unspecifiede trocar quando o complacente reclama ...? Talvez seja por isso que eles existem nos cabeçalhos da Apple?

Curiosamente, algo colocou isso no meu código ... Eu detesto sublinhados no código (cara do estilo Apple C ++ da velha escola), por isso estou absolutamente certo de que não os digitei, mas eles apareceram (um exemplo de vários):

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

E ainda mais interessante, onde inseriu o __nullable está errado ... (eek @!)

Eu realmente gostaria de poder usar apenas a versão sem sublinhado, mas aparentemente isso não ocorre com o compilador, pois isso é sinalizado como um erro:

typedef void ( ^ DidReceiveChallengeBlock ) ( NSURLSessionAuthChallengeDisposition disposition,
                                          NSURLCredential * nonnull  credential );
William Cerniuk
fonte
2
Não sublinhado só pode ser usado diretamente após um parêntese de abertura, ou seja, (não nulo ... Apenas para tornar a vida mais interessante, tenho certeza.
Elise van Looij
Como faço para que um ID <> não seja nulo? Eu sinto que esta resposta contém muito conhecimento, mas falta clareza.
Fizzybear #
1
@fizzybear é certo que normalmente tomo a abordagem oposta. Os ponteiros são meus amigos e eu não tive problemas com ponteiros "nulos" / "nulos" desde o início dos anos 90. Eu gostaria de poder fazer toda a coisa anulável / nilable simplesmente desaparecer. Mas, ao ponto, a resposta real está nas 6 primeiras linhas da resposta. Mas sobre a sua pergunta: não é algo que eu faria (sem críticas), então simplesmente não sei.
William Cerniuk 8/11/19