Разница между 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
) если иное не отмечено.
К сожалению, есть и исключения из этого...
typedef
s не предполагается__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 );