Есть ли правильный способ определить, что NSNumber получен из Bool с использованием Swift?

NSNumber, содержащий Bool, легко спутать с другими типами, которые можно обернуть в класс NSNumber:

NSNumber(bool:true).boolValue // true
NSNumber(integer: 1).boolValue // true
NSNumber(integer: 1) as? Bool // true
NSNumber(bool:true) as? Int // 1

NSNumber(bool:true).isEqualToNumber(1) // true
NSNumber(integer: 1).isEqualToNumber(true) // true

Однако информация о его исходном типе сохраняется, как мы видим здесь:

NSNumber(bool:true).objCType.memory == 99 // true
NSNumber(bool:true).dynamicType.className() == "__NSCFBoolean" // true
NSNumber(bool:true).isEqualToValue(true) || NSNumber(bool:true).isEqualToValue(false) //true

Вопрос в том, какой из этих подходов является лучшим (и/или самым безопасным) подходом к определению того, когда Bool был заключен в NSNumber, а не во что-то еще? Все ли одинаково действительны? Или есть другое, лучшее решение?


person sketchyTech    schedule 13.05.2015    source источник


Ответы (3)


Вы можете задать тот же вопрос для Objective-C, и вот ответ в Objective-C, который вы можете вызвать или перевести на Swift.

NSNumber соединен с CFNumberRef бесплатным мостом, что является еще одним способом сказать, что объект NSNumber на самом деле является объектом CFNumber (и наоборот). Теперь CFNumberRef имеет специальный тип для логических значений, CFBooleanRef, и он используется при создании логического CFNumberRef, также известного как NSNumber *... Итак, все, что вам нужно сделать, это проверить, является ли ваш NSNumber * экземпляром CFBooleanRef:

- (BOOL) isBoolNumber:(NSNumber *)num
{
   CFTypeID boolID = CFBooleanGetTypeID(); // the type ID of CFBoolean
   CFTypeID numID = CFGetTypeID((__bridge CFTypeRef)(num)); // the type ID of num
   return numID == boolID;
}

Примечание. Вы можете заметить, что объекты NSNumber/CFNumber, созданные из логических значений, на самом деле являются предопределенными постоянными объектами; один для YES, один для NO. У вас может возникнуть соблазн полагаться на это для идентификации. Однако в настоящее время это кажется правдой и показано в исходном коде Apple, насколько нам известно, он не задокументирован, поэтому на него нельзя полагаться.

ХТН

Дополнение

Быстрый перевод кода (от GoodbyeStackOverflow):

func isBoolNumber(num:NSNumber) -> Bool
{
    let boolID = CFBooleanGetTypeID() // the type ID of CFBoolean
    let numID = CFGetTypeID(num) // the type ID of num
    return numID == boolID
}
person CRD    schedule 13.05.2015
comment
Я отметил это как правильный ответ, поскольку вы предоставили рабочее решение. Поскольку его нет в Swift, я отредактировал ваш ответ, включив в него код Swift (просто жду экспертной оценки этого редактирования). Спасибо. - person sketchyTech; 14.05.2015
comment
@GoodbyeStackOverflow — одобрил ваш перевод. - person CRD; 14.05.2015

Первый - правильный.

NSNumber — это класс Objective-C. Он создан для Objective-C. Он хранит тип, используя кодировки типов Objective-C. Итак, в Objctive-C лучшим решением будет:

number.objCType[0] == @encoding(BOOL)[0] // or string compare, what is not necessary here

Это гарантирует, что изменение кодировки типа будет работать после повторной компиляции.

Насколько я знаю, у вас нет @encoding() в Swift. Таким образом, вы должны использовать литерал. Однако это не сломается, потому что @encoding() заменяется во время компиляции, а изменение кодировок приведет к поломке скомпилированного кода. Вряд ли.

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

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

person Amin Negm-Awad    schedule 13.05.2015
comment
Проблема в том, что @encode(BOOL), @encode(signed char) и @encode(char) все возвращают c/99 (при условии, что -funsigned-char не было передано компилятору). - person dreamlax; 13.05.2015
comment
Это свойство NSNumber нельзя изменить. И я не вижу в этом проблемы. - person Amin Negm-Awad; 13.05.2015
comment
Потому что OP специально спрашивал об определении того, является ли NSNumber оболочкой объекта BOOL а не чего-то еще. - person dreamlax; 13.05.2015
comment
Да, но посмотрите на его третье решение. BOOL уже давно работают без проблем. Оба по-прежнему являются целыми числами. Я не думаю, что это проблема. - person Amin Negm-Awad; 13.05.2015

Не полагайтесь на имя класса, так как оно, скорее всего, принадлежит кластеру классов и является деталью реализации (и, следовательно, может быть изменено).

К сожалению, тип Objective-C BOOL изначально был просто typedef для signed char в C, который всегда кодируется как c (это значение 99, которое вы видите, поскольку c в ASCII равно 99).

Я считаю, что в современном Objective-C тип BOOL является фактическим логическим типом (т. е. больше не просто определением типа для signed char), но для совместимости он по-прежнему кодируется как c, когда передается @encode().

Таким образом, невозможно определить, относилось ли 99 к signed char или к BOOL, поскольку NSNumber это одно и то же.

Может быть, если вы объясните, почему вам нужно знать, было ли NSNumber изначально BOOL, может быть лучшее решение.

person dreamlax    schedule 13.05.2015
comment
Причина в том, что при использовании NSJSONSerialization Bools импортируются как экземпляры NSNumber, и мне нужно различать их, чтобы работать с JSON безопасным способом, например. только позволяя Bool изменяться на true или false, а не на 10 или 100 или 999,99, а также убедиться, что они преобразованы обратно в true и false, а не в 1 и 0 в экспортированном JSON. - person sketchyTech; 13.05.2015
comment
При преобразовании обратно в JSON вызовите -boolValue на NSNumber, чтобы получить 1 или 0. - person Zev Eisenberg; 13.05.2015
comment
Насколько я понимаю из документации, boolValue просто обеспечивает логическую интерпретацию любого экземпляра NSNumber: значение 0 всегда означает ложь, а любое ненулевое значение интерпретируется как истина. Но я хочу сохранить истинное/ложное представление и не путать логические значения и числа. - person sketchyTech; 13.05.2015