How to determine the true data type of an NSNumber?

让人想犯罪 __ 提交于 2019-11-27 09:32:38
aust

Simple answer: You can't.

In order to do what you're asking, you'll need to keep track of the exact type on your own. NSNumber is more of a "dumb" wrapper in that it helps you use standard numbers in a more objective way (as Obj-C objects). Using solely NSNumber, -objCType is your only way. If you want another way, you'd have to do it on your own.

Here are some other discussions that may be of help:

get type of NSNumber

What's the largest value an NSNumber can store?

Why is longLongValue returning the incorrect value

NSJSONSerialization unboxes NSNumber?

As documented in NSDecimalNumber.h, NSDecimalNumber always returns "d" for it's return type. This is expected behavior.

- (const char *)objCType NS_RETURNS_INNER_POINTER;
    // return 'd' for double

And also in the Developer Docs:

Returns a C string containing the Objective-C type of the data contained in the
receiver, which for an NSDecimalNumber object is always “d” (for double).

CFNumberGetValue is documented to return false if the conversion was lossy. In the event of a lossy conversion, or when you encounter an NSDecimalNumber, you will want to fall back to using the stringValue and then use sqlite3_bind_text to bind it (and use sqlite's column affinity).

Something like this:

NSNumber *number = ...
BOOL ok = NO;

if (![number isKindOfClass:[NSDecimalNumber class]]) {
    CFNumberType numberType = CFNumberGetType(number);

    if (numberType == kCFNumberFloat32Type ||
        numberType == kCFNumberFloat64Type ||
        numberType == kCFNumberCGFloatType)
    {
        double value;
        ok = CFNumberGetValue(number, kCFNumberFloat64Type, &value);

        if (ok) {
            ok = (sqlite3_bind_double(pStmt, idx, value) == SQLITE_OK);
        }

    } else {
        SInt64 value;
        ok = CFNumberGetValue(number, kCFNumberSInt64Type, &value);

        if (ok) {
            ok = (sqlite3_bind_int64(pStmt, idx, value) == SQLITE_OK);
        }
    }
}

// We had an NSDecimalNumber, or the conversion via CFNumberGetValue() was lossy.
if (!ok) {
    NSString *stringValue = [number stringValue];
    ok = (sqlite3_bind_text(pStmt, idx, [stringValue UTF8String], -1, SQLITE_TRANSIENT) == SQLITE_OK);
}

NSJSONSerializer returns:

an integer NSNumber for integers up to 18 digits

an NSDecimalNumber for integers with 19 or more digits

a double NSNumber for numbers with decimals or exponent

a BOOL NSNumber for true and false.

Compare directly with the global variables kCFBooleanFalse and kCFBooleanTrue (spelling might be wrong) to find booleans. Check isKindOfClass:[NSDecimalNumber class] for decimal numbers; these are actually integers. Test

strcmp (number.objCType, @encode (double)) == 0

for double NSNumbers. This will unfortunately match NSDecimalNumber as well, so test that first.

Ok--It's not 100% ideal, but you add a little bit of code to SBJSON to achieve what you want.

1. First, add NSNumber+SBJson to the SBJSON project:

NSNumber+SBJson.h

@interface NSNumber (SBJson)
@property ( nonatomic ) BOOL isDouble ;
@end

NSNumber+SBJson.m

#import "NSNumber+SBJSON.h"
#import <objc/runtime.h>

@implementation NSNumber (SBJson)

static const char * kIsDoubleKey = "kIsDoubleKey" ;

-(void)setIsDouble:(BOOL)b
{
    objc_setAssociatedObject( self, kIsDoubleKey, [ NSNumber numberWithBool:b ], OBJC_ASSOCIATION_RETAIN_NONATOMIC ) ;
}

-(BOOL)isDouble
{
    return [ objc_getAssociatedObject( self, kIsDoubleKey ) boolValue ] ;
}

@end

2. Now, find the line in SBJson4StreamParser.m where sbjson4_token_real is handled. Change the code as follows:

case sbjson4_token_real: {
    NSNumber * number = @(strtod(token, NULL)) ;
    number.isDouble = YES ;
    [_delegate parserFoundNumber:number ];
    [_state parser:self shouldTransitionTo:tok];
    break;
}

note the bold line... this will mark a number created from a JSON real as a double.

3. Finally, you can check the isDouble property on your number objects decoded via SBJSON

HTH

edit:

(Of course you could generalize this and replace the added isDouble with a generic type indicator if you like)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!