Різниця між зведеними, __німітними та _Нелабільними в Objective-C


154

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

Але я все ще плутаюсь, чому у нас так багато приміток, які в основному роблять те саме.

Отже, моє питання:

Яка точна різниця між цими анотаціями, як їх правильно розмістити і чому?


3
Я прочитав цю публікацію, але вона не пояснює різниці і чому у нас зараз 3 різних типи анотацій, і я дуже хочу зрозуміти, чому вони продовжували додавати третій тип.
Legoless

2
Це насправді не допомагає @ Cy-4AH, і ти це знаєш. :)
Legoless

@Legoless, ти впевнений, що читаєш це уважно? це точно пояснює, де і як ви повинні їх використовувати, які аудиторські сфери, коли ви можете використовувати інший для кращої читабельності, причин сумісності тощо, тощо. Ви можете не знати, що вам справді подобається запитувати, але відповідь чітко за посиланням. це, можливо, тільки я, але я не думаю, що будь-яке подальше пояснення було б необхідним для пояснення їх мети, копіювання та вставки цього простого пояснення тут, як відповіді, було б справді незручним. :(
holex

2
Це чітко прояснює певні частини, але ні, я досі не розумію, чому у нас не просто перші примітки. Це лише пояснює, чому вони перейшли від __nullable до _Nullable, але не чому нам навіть потрібен _Nullable, якщо у нас є незмінні. І це також не пояснює, чому Apple все ще використовує __nullable у власному коді.
Legoless

Відповіді:


152

З clang документації :

Кваліфікатори нульової (типової) форми виражають, чи може значення даного типу вказівника бути нульовим ( _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)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
Здається, що версії підкреслення можна використовувати скрізь, тому, напевно, я послідовно їх використовуватиму, а не використовую підкреслені версії в одних місцях та версії без підкреслення в інших. Правильно?
Ваддаді Картік

@KartickVaddadi так, правильно, ви можете послідовно використовувати або окремі підкреслені версії, або подвійні підкреслені версії.
Крістік

_Null_unspecifiedу Swift це перекладається на необов'язковий? необов'язковий чи що?
Мед

1
@Honey _Null_unspecified імпортується в Swift як неявно розгорнуте необов’язково
Крістік

1
@ Крістік ага. Я думаю, це його значення за замовчуванням ... тому що, коли я не вказував, я отримував неявно розгорнуту необов'язково ...
Мед

28

З блогу Swift :

Ця функція була вперше випущена в Xcode 6.3 із ключовими словами __nullable та __nonnull. Через потенційні конфлікти із сторонніми бібліотеками ми змінили їх у Xcode 7 на _Nullable та _Nonnull, які ви бачите тут. Однак, для сумісності з Xcode 6.3 ми заздалегідь визначили макроси __nullable та __nonnull для розширення до нових імен.


4
Коротше кажучи, одиничні та подвійні підкреслені варіанти однакові.
Ваддаді Картік

4
Також задокументовано у примітках до випуску Xcode 7.0: "Класифікатори подвійного підкреслення нульовості (__nullable, __nonnull та __null_unspecified) були перейменовані на використання єдиного підкреслення з великої літери: _Nullable, _Nonnull та _Null_unspecified, попередньо). відображення зі старих подвійних невизначених імен до нових імен для сумісності з джерелами. (21530726) "
Cosyn

25

Ця стаття мені дуже сподобалась , тому я просто показую те, що писав автор: https://swiftunboxed.com/interop/objc-nullability-annotations/

  • null_unspecified:мости до Swift неявно розгорнуто необов'язково. Це за замовчуванням .
  • nonnull: значення не буде нульовим; мости до регулярної довідки.
  • nullable: значення може бути нульовим; мости до факультативного.
  • null_resettable: значення ніколи не може бути нульовим при читанні, але ви можете встановити його на нуль, щоб скинути його. Застосовується лише для властивостей.

Наведені вище позначення відрізняються тим, чи використовуєте ви їх у контексті властивостей чи функцій / змінних:

Показники проти позначення властивостей

Автор статті також надав хороший приклад:

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

12

Дуже зручно

NS_ASSUME_NONNULL_BEGIN 

і закриття с

NS_ASSUME_NONNULL_END 

Це зведе нанівець потребу в рівні коду 'nullibis' :-), оскільки це має сенс вважати, що все є недійсним (абоnonnull або _nonnullабо __nonnull), якщо не зазначено інше.

На жаль, і в цьому є винятки ...

  • typedefs не вважаються такими, що є __nonnull (зауважте, nonnullце, здається, не працює, доводиться використовувати це некрасиво напівбратом)
  • id * потребує явного нулібі, але вау податку на гріх ( _Nullable id * _Nonnull <- здогадайтесь, що це означає ...)
  • NSError ** завжди вважається нульовим

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

Цікаво, що щось вклало в мій код ... Я ненавиджу підкреслення коду (старий шкільний хлопець у стилі Apple C ++), тому я абсолютно впевнений, що не ввів їх, але вони з'явилися (один приклад з кількох):

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

І ще цікавіше, де він вставив __nullable - це неправильно ... (eek @!)

Я дуже хочу, щоб я міг просто скористатися версією, що не підкреслює, але, мабуть, це не летить з компілятором, оскільки це позначено як помилка:

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

2
Без підкреслення можна використовувати лише безпосередньо після вступних дужок, тобто (не потрібне ... Просто, щоб зробити життя цікавішим, я впевнений.
Elise van Looij

Як зробити ідентифікатор <> nonnull? Я відчуваю, що ця відповідь містить багато знань, але їй не вистачає ясності.
fizzybear

1
@fizzybear, правда, я зазвичай приймаю протилежний підхід. Покажчики - це мій друг, і у мене не було проблем з "нульовим" / "нульовим" покажчиком з початку 90-х. Я б хотів, щоб я міг змусити всю мінливу / мінливу річ просто піти. Але до речі, справжня відповідь є в перших 6 рядках відповіді. Але щодо вашого питання: не те, що я би робив (без критики), тому я просто не знаю.
Вільям Чернюк
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.