Разница между обнуляемыми, __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 );