简体   繁体   中英

ObjectiveC test if int is long or not / CGFloat is float or double

Apple now requires all iOS code to be 32/64 bit dualistic. That's great - except there's a few of their libraries that don't fully support the duality.

eg one project uses NSScanner, which supports "scanInteger" (correct) but no "scanCGFloat" - instead you have to "scanFloat" or "scanDouble" (encoded in the method names!).


UPDATE: NSScanner is a nastier example than I realised; it takes pointers as args.

- (BOOL)scanFloat:(float *)result;
- (BOOL)scanDouble:(double *)result;

...ugh.


It affects a whole bunch of stuff - another example is math.h (similarly: float vs double is encoded in function names), although there you can at least switch to tgmath.h and get rid of the "type-is-in-the-name" silliness.

What's the correct, general, solution to this problem?

C11 has introduced a new feature "Generic selection" that can be used to let the compiler choose the right method, depending on the type of CGFloat .

Written as a NSScanner category method:

@implementation NSScanner (MyCategory)

-(BOOL) myScanCGFloat:(CGFloat *)cgFloatValue
{
    return _Generic(*cgFloatValue,
                    double: [self scanDouble:(double *)cgFloatValue],
                    float: [self scanFloat:(float *)cgFloatValue]);
}

@end

Remarks:

  • The _Generic keyword is described in "6.5.1.1 Generic selection" of the C11 standard ( http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf ). Another description is here: http://www.robertgamble.net/2012/01/c11-generic-selections.html .
  • Selecting the matching code is done by the compiler , not at runtime.
  • The selection checks if CGFloat is compatible to float or double , not if the current target architecture is 32-bit or 64-bit.
  • This solution does not depend on any preprocessor macros.
  • As of Xcode 5.0.2, Clang supports the _Generic keyword, even in the default GNU99 mode. I have not tested earlier Xcode/Clang versions.

Previous answer: One possible solution would be do mimic the definition of CGFloat and let the preprocessor choose the correct version:

CGFloat f;

#if __LP64__
    [scanner scanDouble:&f];
#else
    [scanner scanFloat:&f];
#endif

Or you define a custom macro:

#if __LP64__
#define scanCGFloat scanDouble
#else
#define scanCGFloat scanFloat
#endif

// ...
CFFloat f;
[scanner scanCGFloat:&f];

Alternatively, use a temporary variable:

double tmp;
[scanner scanDouble:&tmp];
f = tmp;

For reference (and I feel this is a poor, cludgy solution), my best idea so far is to do:

NB -- I'm using NSScanner as an example, but it's a general problem/solution!

if( sizeof( var ) <= 4 )
    [scanner scanFloat:&var];
else
    [scanner scanDouble:&var];

...but this will NOT fix the compiler-errors (on both platforms), it'll simply make it "do the right thing at runtime" as a hack. Surely not a good solution?

Ah, routing around in headers, I just found this (NOT mentioend in Apple's official "upgrade to 64bit" docs!!!)

#define CGFLOAT_IS_DOUBLE 1

#define CGFLOAT_IS_DOUBLE 0

...so I guess this is the thing to use?

The simplest solution is to scan as a double and then cast the result to CGFloat.

-(CGFloat) scanCGFloat
{

    double myDouble;
    [scanner scanDouble: &myDouble];
    return (CGFloat) myDouble;
}

Use a similar trick for NSInteger and NSUInteger, always scan as a (unsigned) long and cast to NS(U)Integer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM