Разница между Nullable, __nullable и _Nullable в Objective-C

154

В Xcode 6.3 появились новые аннотации для лучшего выражения намерений API в Objective-C (и, конечно, для обеспечения лучшей поддержки Swift). Эти аннотации были, конечно nonnull, nullableи null_unspecified.

Но с Xcode 7 появляется много предупреждений, таких как:

В указателе отсутствует спецификатор типа обнуляемости (_Nonnull, _Nullable или _Null_unspecified).

В дополнение к этому Apple использует другой тип спецификаторов обнуляемости, помечая их C-код ( источник ):

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

Итак, подведем итог, теперь у нас есть эти 3 различные аннотации обнуляемости:

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

Несмотря на то, что я знаю, почему и где использовать какую аннотацию, меня немного смущает, какой тип аннотации мне следует использовать, где и почему. Вот что я мог бы собрать:

  • Для свойств следует использовать nonnull, nullable, null_unspecified.
  • Для параметров методы следует использовать nonnull, nullable, null_unspecified.
  • Для методов C следует использовать __nonnull, __nullable, __null_unspecified.
  • Для других случаев, например, двойные указатели я должен использовать _Nonnull, _Nullable, _Null_unspecified.

Но я все еще не понимаю, почему у нас так много аннотаций, которые в основном делают одно и то же.

Итак, мой вопрос:

В чем разница между этими аннотациями, как правильно их разместить и почему?

Legoless
источник
3
Я читал этот пост, но он не объясняет разницу и почему у нас есть 3 различных типа аннотаций сейчас, и я действительно хочу понять, почему они продолжили добавлять третий тип.
Legoless
2
Это действительно не помогает @ Cy-4AH, и вы это знаете. :)
Legoless
@Legoless, ты уверен, что внимательно прочитал? он точно объясняет, где и как вы должны их использовать, каковы области аудита, когда вы можете использовать другую для лучшей читабельности, из соображений совместимости и т. д. и т. д. ... вы можете не знать, что вам действительно нравится спрашивать, но Ответ явно по ссылке. это может быть только я, но я не чувствую, что какое-либо дальнейшее объяснение было бы необходимо для объяснения их цели, копирование и вставка этого простого объяснения здесь в качестве ответа здесь было бы действительно неловко, я полагаю. :(
Holex
2
Это проясняет некоторые части, но нет, я все еще не понимаю, почему у нас нет только первых аннотаций. Это только объясняет, почему они перешли от __nullable к _Nullable, но не объясняет, почему нам даже нужно _Nullable, если мы имеем nullable. И это также не объясняет, почему Apple по-прежнему использует __nullable в своем собственном коде.
Legoless

Ответы:

152

Из clang документации :

Спецификаторы обнуляемости (типа) выражают, может ли значение заданного типа указателя быть нулевым ( _Nullableквалификатор), не имеет определенного значения для нулевого ( _Nonnullквалификатор), или для которого не определено назначение нуля ( _Null_unspecifiedквалификатор) , Поскольку классификаторы допустимости пустых выражены в системе типа, они являются более общими , чем nonnullи returns_nonnullатрибуты, позволяя одному выразить (например) обнуляемый указатель на массив указателей непустых. Спецификаторы обнуляемости пишутся справа от указателя, к которому они применяются.

, и

В Objective-C существует альтернативное написание для квалификаторов обнуляемости, которые можно использовать в методах и свойствах Objective-C с использованием контекстно-зависимых ключевых слов без подчеркивания.

Таким образом, для возвратов методов и параметров вы можете использовать версии с двойным подчеркиванием __nonnull/ __nullable/ __null_unspecifiedвместо одинарных или подчеркнутых версий . Разница в том, что одинарные и двойные подчеркнутые должны быть помещены после определения типа, в то время как не подчеркнутые должны быть помещены перед определением типа.

Таким образом, следующие объявления эквивалентны и являются правильными:

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

Для параметров:

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

Для свойств:

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

Однако, все усложняется, когда задействованы двойные указатели или блоки, возвращающие что-то отличное от void, так как здесь не допускаются знаки подчеркивания:

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

Аналогично методам, которые принимают блоки в качестве параметров, обратите внимание, что спецификатор nonnull/ nullableприменяется к блоку, а не к его типу возврата, поэтому следующие условия эквивалентны:

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

Если блок имеет возвращаемое значение, то вы вынуждены перейти к одной из версий подчеркивания:

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

В заключение вы можете использовать любой из них, при условии, что компилятор может определить элемент для назначения классификатора.

Cristik
источник
2
Кажется, что версии подчеркивания могут использоваться везде, поэтому я полагаю, что буду использовать их последовательно, вместо того, чтобы использовать версии с подчеркиванием в некоторых местах и ​​версии без подчеркивания в других. Верный?
Ваддади Картик
@KartickVaddadi Да, правильно, вы можете последовательно использовать либо версию с одним подчеркиванием, либо версию с двойным подчеркиванием.
Cristik
_Null_unspecifiedв Swift это переводится как необязательный? необязательно или как?
Мед
1
@Honey _Null_unspecified импортируется в Swift как неявно развернутая опция
Cristik,
1
@ Кристик ага. Я предполагаю, что это значение по умолчанию ... потому что, когда я не указал, я получал Неявно развернутый необязательно ...
Дорогой
28

Из блога Swift :

Эта функция была впервые выпущена в Xcode 6.3 с ключевыми словами __nullable и __nonnull. Из-за потенциальных конфликтов со сторонними библиотеками мы изменили их в Xcode 7 на _Nullable и _Nonnull, которые вы видите здесь. Однако для совместимости с Xcode 6.3 мы предопределили макросы __nullable и __nonnull для расширения до новых имен.

Бен Томас
источник
4
В двух словах, одинарные и двойные подчеркнутые версии идентичны.
Ваддади Картик
4
Также задокументировано в заметках о выпуске Xcode 7.0: «Спецификаторы обнуляемости с двойным подчеркиванием (__nullable, __nonnull и __null_unspecified) были переименованы, чтобы использовать одно подчеркивание с заглавной буквой: _Nullable, _Nonnull и _Null_unspecified, соответственно). отображение старых двойных неопределенных имен на новые имена для совместимости с исходным кодом. (21530726) "
Cosyn
25

Мне очень понравилась эта статья , поэтому я просто показываю, что написал автор: https://swiftunboxed.com/interop/objc-nullability-annotations/

  • null_unspecified:мосты к Свифту неявно развернуты необязательно. Это по умолчанию .
  • nonnull: значение не будет равно нулю; мосты к регулярной ссылке.
  • nullable: значение может быть ноль; мосты по желанию.
  • null_resettable: значение никогда не может быть равно нулю при чтении, но вы можете установить его равным нулю, чтобы сбросить его. Относится только к свойствам.

Приведенные выше обозначения отличаются, используете ли вы их в контексте свойств или функций / переменных:

Указатели против свойств

Автор статьи также привел хороший пример:

// 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
источник
12

Очень удобно это

NS_ASSUME_NONNULL_BEGIN 

и в заключение

NS_ASSUME_NONNULL_END 

Это сведет на нет необходимость в уровне кода 'nullibis' :-), так как имеет смысл предполагать, что все не является нулевым (или nonnullили _nonnullили __nonnull), если не указано иное.

К сожалению, есть и исключения из этого ...

  • typedefs не предполагается __nonnull(обратите внимание, nonnullпохоже, не работает, должны использовать его уродливый сводный брат)
  • id *нужен явный нулиби, но вау налог на грех ( _Nullable id * _Nonnull<- угадайте, что это значит ...)
  • NSError ** всегда предполагается обнуляемым

Таким образом , за исключением исключений и непоследовательными ключевыми словами , вызывающим такую же функциональность, возможно, подход заключается в использовании уродливых версий __nonnull/ __nullable/ __null_unspecifiedи свопа , когда компилятор жалуется ...? Может быть, поэтому они существуют в заголовках Apple?

Интересно, что кое-что вставило это в мой код ... Я ненавижу подчеркивать в коде (парень старой школы в стиле Apple C ++), поэтому я абсолютно уверен, что я их не печатал, но они появились (один пример из нескольких):

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

И еще интереснее то, куда он вставил __nullable - неправильно ... (eek @!)

Я действительно хотел бы просто использовать версию без подчеркивания, но, видимо, это не работает с компилятором, поскольку это помечено как ошибка:

typedef void ( ^ DidReceiveChallengeBlock ) ( NSURLSessionAuthChallengeDisposition disposition,
                                          NSURLCredential * nonnull  credential );
Уильям Чернюк
источник
2
Не подчеркивание можно использовать только после открывающей скобки, т.
Е. (
Как сделать идентификатор <> ненулевым? Я чувствую, что этот ответ содержит много знаний, но ему не хватает ясности.
fizzybear
1
@fizzybear по общему признанию я обычно принимаю противоположный подход. Указатели - мой друг, и у меня не было проблем с указателями «ноль» / «ноль» с начала 90-х годов. Хотелось бы, чтобы вся эта штука, которую можно обнулить / убить, просто ушла. Но по сути, реальный ответ находится в первых 6 строках ответа. Но по поводу твоего вопроса: не то, что я бы сделал (без критики), поэтому я просто не знаю.
Уильям Чернюк