Разница между обнуляемыми, __nullable и _Nullable в Objective-C

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

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

У указателя отсутствует спецификатор типа nullability (_Nonnull, _Nullable или _Null_unspecified).

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

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

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

  • 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.

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

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

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

Ответ 1

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

Квалификаторы nullability (type) выражают, может ли значение заданного типа указателя быть нулевым (_Nullable), не имеет определенного значения для null (критерий _Nonnull) или для которого цель null неясен (определитель _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

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

Ответ 2

Из журнала Swift:

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

Ответ 3

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

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

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

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

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

// 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;

Ответ 4

Очень удобно

NS_ASSUME_NONNULL_BEGIN 

и закрытие с помощью

NS_ASSUME_NONNULL_END 

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

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

  • typedef не предполагается __nonnull (обратите внимание, nonnull, похоже, не работает, придется использовать его уродливым братом)
  • id * нуждается в явном nullibi, но wow the sin-tax (_Nullable id * _Nonnull > предположим, что это означает...)
  • NSError ** всегда считается нулевым

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

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

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

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

Мне очень жаль, что я не могу использовать версию без подчеркивания, но, по-видимому, не летает с компилятором, поскольку это помечено как ошибка:

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