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

В 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,

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

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

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

4 ответа

От clang документация:

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

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

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

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

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

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

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

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

Из блога Swift:

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

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

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 );
Другие вопросы по тегам