sketchyTech
sketchyTech

Reputation: 5916

Is there a correct way to determine that an NSNumber is derived from a Bool using Swift?

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

Answers (3)

CRD
CRD

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

dreamlax
dreamlax

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

Amin Negm-Awad
Amin Negm-Awad

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

Related Questions