Reputation: 5916
An NSNumber containing a Bool is easily confused with other types that can be wrapped in the NSNumber class:
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
However, information about its original type is retained, as we can see here:
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
The question is: which of these approaches is the best (and/or safest) approach to determining when a Bool has been wrapped within an NSNumber rather than something else? Are all equally valid? Or, is there another, better solution?
Upvotes: 19
Views: 4603
Reputation: 53010
You can ask the same question for Objective-C, and here is an answer in Objective-C - which you can call from, or translate into, Swift.
NSNumber
is toll-free bridged to CFNumberRef
, which is another way of saying an NSNumber
object is in fact a CFNumber
one (and vice-versa). Now CFNumberRef
has a specific type for booleans, CFBooleanRef
, and this is used when creating a boolean CFNumberRef
aka NSNumber *
... So all you need to do is check whether your NSNumber *
is an instance of 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;
}
Note: You may notice that NSNumber
/CFNumber
objects created from booleans are actually pre-defined constant objects; one for YES
, one for NO
. You may be tempted to rely on this for identification. However, though is currently appears to be true, and is shown in Apple's source code, to our knowledge it is not documented so should not be relied upon.
HTH
Addendum
Swift code translation (by 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
}
Upvotes: 32
Reputation: 95405
Don't rely on the class name as it likely belongs to a class cluster, and it is an implementation detail (and therefore subject to change).
Unfortunately, the Objective-C BOOL
type was originally a just typedef for a signed char
in C, which is always encoded as c
(this is the 99
value you are seeing, since c
in ASCII is 99).
In modern Objective-C, I believe the BOOL
type is an actual Boolean type (i.e. no longer just a typedef for signed char
) but for compatibility, it still encodes as c
when given to @encode()
.
So, there's no way to tell whether the 99
originally referred to a signed char
or a BOOL
, as far as NSNumber
is concerned they are the same.
Maybe if you explain why you need to know whether the NSNumber
was originally a BOOL
, there may be a better solution.
Upvotes: 0
Reputation: 16660
The first one is the correct one.
NSNumber
is an Objective-C class. It is built for Objective-C. It stores the type using the type encodings of Objective-C. So in Objctive-C the best solution would be:
number.objCType[0] == @encoding(BOOL)[0] // or string compare, what is not necessary here
This ensures that a change of the type encoding will work after re-compile.
AFAIK you do not have @encoding()
in Swift. So you have to use a literal. However, this will not break, because @encoding()
is replaced at compile time and changing the encodings would break with compiled code. Unlikely.
The second approach uses a internal identifier. This is likely subject of change.
I think the third approach will have false positives.
Upvotes: 0