Что такое nil, но не nil при вставке в NSMutableDictionary

Я столкнулся с каким-то странным поведением на iPhone 6, iOS 8.3.

appVersion — это передаваемый параметр NSString*.

  NSLog(@"A:%@:%d",appVersion,(int)appVersion.length);
  if (!appVersion)
    NSLog(@"a");
  if (appVersion == 0)
    NSLog(@"b");
  if (appVersion == nil)
    NSLog(@"c");
  if (appVersion == NULL)
    NSLog(@"d");
  if (appVersion == Nil)
    NSLog(@"e");
  if ([appVersion isEqual:[NSNull null]])
    NSLog(@"f");

  NSString* av = [NSString stringWithFormat:@"%@",appVersion];
  if ([av isEqualToString:@"(null)"])
    NSLog(@"g");
  if (((int)appVersion) == 0)
    NSLog(@"h");

  if (appVersion) {
    NSLog(@"B:%@:%d",appVersion,(int)appVersion);
    params[@"appversion"] = appVersion;
  }

Релизная сборка приложения возвращает:

A:(null):0
g
h
B:(null):0

а затем вылетает («объект не может быть нулевым (ключ: версия приложения)»).

Отладочная сборка возвращает:

a
b
c
d
e
g
h

Что такое ноль, но не нуль?


person aepryus    schedule 18.04.2015    source источник
comment
Как вы присваиваете значение для appVersion??   -  person DilumN    schedule 18.04.2015
comment
@BC_Dilum, он извлекается из NSDictionary, возвращенного из документа с диванной базы.   -  person aepryus    schedule 18.04.2015
comment
Можете ли вы добавить этот код?   -  person DilumN    schedule 18.04.2015
comment
@Cristik, в какой-то степени это приложение для iOS, но appVersion не сбрасывается; это уже какая-то версия 0 для начала, и немного сложно представить, что другой поток попадет прямо между if и set в 100% случаев.   -  person aepryus    schedule 18.04.2015
comment
Журнал appVersion class   -  person rmaddy    schedule 18.04.2015
comment
@Cristic да, это многопоточность.   -  person aepryus    schedule 18.04.2015
comment
@ Кристик Нет, это установлено один раз.   -  person aepryus    schedule 18.04.2015
comment
@rmaddy NSLog(@%@,appVersion.class) печатает: (null)   -  person aepryus    schedule 18.04.2015
comment
@BC_Dilum значение извлекается из другого NSMutableDictionary, что означает, что оно извлекается из NSMutableDictionary, который не содержит соответствующего ключа.   -  person aepryus    schedule 18.04.2015
comment
Кажется, это какая-то странная проблема с оптимизацией. Код работает, как и ожидалось, в режиме отладки, но в режиме выпуска он кажется одновременно нулевым и не нулевым. Хотя это кажется ненулевым только для операторов if в режиме выпуска. Можете ли вы создать приложение только с этим кодом, демонстрирующим проблему?   -  person rmaddy    schedule 18.04.2015
comment
Я поддерживаю просьбу обновить ваш вопрос с кодом, который назначает appVersion.   -  person rmaddy    schedule 18.04.2015
comment
@rmaddy Работая над поиском полезного кода, я, возможно, нашел проблему. Я отправлю, как только я отслеживать его немного больше.   -  person aepryus    schedule 18.04.2015


Ответы (3)


Я работаю с каким-то устаревшим кодом и не заметил разницы в подписи метода между файлами .h и .m.

В файле .h есть:

- (void) verifyWinner:(NSString*)baseAcctId
           appVersion:(NSString*)appVersion
           onComplete:(OnCompleteWinnerVerifier)onComplete __attribute__((nonnull));

Я предполагаю, что первоначальный разработчик хотел предотвратить установку onComplete на ноль. Однако по какой-то причине __attribute__((nonnull)) связано с каждым из параметров.

Из-за тега __attribute__ XCode оптимизирует все проверки != nil для сборки выпуска, что приводит к сбою.

Эта проблема возникла только сейчас с XCode 6.3. Итак, возможно, Apple недавно добавила оптимизацию или же внесла ошибку в 6.3, которая связывает __attribute__ с каждым из параметров, а не только с параметрами, рядом с которыми он находится (во всяком случае, в целях оптимизации).

person aepryus    schedule 18.04.2015
comment
Хороший. Поэтому, если параметр объявлен как ненулевой, вы даже не можете проверить его во время выполнения, потому что проверка удаляется оптимизирующим компилятором. Другими словами, передача nil ненулевому параметру является поведением undefined. - person gnasher729; 18.04.2015
comment
@ gnasher729 Да, и если он будет связан с каждым из параметров, это может вызвать много проблем. Я, вероятно, должен подать отчет об ошибке. - person aepryus; 18.04.2015
comment
Хорошая находка. Обязательно отправьте отчет об ошибке с простым воспроизводимым фрагментом кода, демонстрирующим проблему. - person rmaddy; 18.04.2015
comment
@JesseRusak Да, я думаю, ты прав. Я только что обнаружил, что синтаксис включает номера параметров. Таким образом, в этом случае он должен был быть определен как __attribute__((nonnull (3) )). - person aepryus; 18.04.2015

Проверьте наличие [NSNull null]

Класс NSNull определяет одноэлементный объект, который вы используете для представления нулевых значений в ситуациях, когда nil запрещено в качестве значения (обычно в объекте коллекции, таком как массив или словарь).

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/NumbersandValues/Articles/Null.html

person JDM    schedule 18.04.2015
comment
NSNull — это моя проверка «f» выше. В обеих версиях возвращается НЕТ. - person aepryus; 18.04.2015
comment
Извините, пропустил это... Попробуйте использовать == , согласно Apple, это имеет значение: экземпляр NSNull семантически эквивалентен nil, однако также важно понимать, что он не равен nil. Поэтому для проверки нулевого значения объекта необходимо выполнить прямое сравнение объектов. - person JDM; 18.04.2015
comment
Выдает предупреждение о времени компиляции: Сравнение различных типов указателей ("NSString *" и "NSNull *") - person aepryus; 18.04.2015
comment
Хорошо, я привел строку к идентификатору, но он все равно вернул НЕТ. - person aepryus; 18.04.2015
comment
Какую информацию вы получите, если поставите точку останова в операторе журнала, если напечатаете переменную? На самом деле я только что понял, что это не может быть ]NSNull null], потому что это не вызовет проблем при вставке в словарь. - person JDM; 18.04.2015
comment
Я не могу воспроизвести проблему для отладочной сборки. :-( - person aepryus; 18.04.2015

Результат выглядит странно. Есть хорошая статья; google для «Что каждый программист должен знать о неопределенном поведении» Криса Латтнера (главный разработчик Swift, поэтому он должен знать, о чем говорит).

Похоже, что после самого первого оператора NSLog оптимизирующий компилятор решил, что appVersion не может быть nil, потому что передача nil в NSLog будет неопределённым поведением. Это объясняет, почему от a до e не печатаются.

Выводится «h», потому что appVersion — это 64-битный указатель, а int — только 32-битный, поэтому преобразование отличного от nil значения appVersion в int может привести к нулевому результату. Оптимизатор не может удалить эту проверку, даже если он уверен, что appVersion не равен нулю.

И поскольку компилятор уверен, что appVersion не равен нулю, последний тест не выполняется, appVersion сохраняется в параметре, и поскольку он равен нулю, происходит сбой.

person gnasher729    schedule 18.04.2015
comment
Но передача nil в NSLog определена. Он просто появляется как ноль для данного спецификатора формата. - person rmaddy; 18.04.2015
comment
Хорошо, я понял это. Я опубликую ответ в ближайшее время. Но, это правильный путь. - person aepryus; 18.04.2015