Има ли правилен начин да се определи, че 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, доколкото ни е известно, той не е документиран, така че не трябва да се разчита на него.

HTH

Допълнение

Превод на Swift код (от 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. Така че в Objective-C най-доброто решение би било:

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

Това гарантира, че промяната на кодирането на типа ще работи след повторно компилиране.

AFAIK нямате @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 е действителен булев тип (т.е. вече не е само typedef за 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 просто предоставя bool интерпретация на всеки екземпляр на NSNumber: стойност 0 винаги означава невярно и всяка ненулева стойност се интерпретира като истина. Но искам да запазя истинското/фалшивото представяне и да не бъркам булетата и числата. - person sketchyTech; 13.05.2015