it-swarm.com.ru

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

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

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

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

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

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.

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

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

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

135
Legoless

От 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

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

132
Cristik

От блог Swift :

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

25
Ben Thomas

Мне очень понравилось эта статья, поэтому я просто показываю, что написал автор: https: // swiftunboxed .com/Interop/ObjC-допустимость пустых-аннотации /

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

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

Pointers vs. Properties notation

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

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

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

NS_ASSUME_NONNULL_BEGIN 

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

NS_ASSUME_NONNULL_END 

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

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

  • typedefs не считается __nonnull (заметьте, nonnull, похоже, не работает, приходится использовать его уродливый сводный брат)
  • id * нужен явный nullibi, но вау налог на грех (_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 );
12
William Cerniuk