2

I'm trying to sync objects over GameCenter, accessing their values with KVC on both sides. Setting numeric values using setValue:forKey: requires them to be NSNumber objects.
NSValue initWithBytes:objCType: gives me NSValue objects even passing encodings for int, float and such.

Do you guys have a better solution instead of checking the encoding manually?

- (NSValue*)smartValueWithBytes:(void*)value objCType:(const char*)type
{
    if (0 == strcmp(type, @encode(int)))
    {
        int tmp;
        memcpy(&tmp, value, sizeof(tmp));
        return [NSNumber numberWithInt:tmp];
    }
    if (0 == strcmp(type, @encode(BOOL)))
    {
        BOOL tmp;
        memcpy(&tmp, value, sizeof(tmp));
        return [NSNumber numberWithBool:tmp];
    }
    //etc...
    return [NSValue valueWithBytes:value objCType:type];
}

If this is the way to go, is NSNumber the only NSValue subclass i need to take care of for KVC?

weezor
  • 2,621
  • 1
  • 16
  • 10
  • 3
    btw--this has a bug. It should be `if ( 0 == strcmp( type, @encode( int ) ) )` – nielsbot Jul 16 '12 at 02:43
  • What happens if you say `[NSNumber valueWithBytes:objCType:]`? Since `NSNumber` is a subclass of `NSValue`, it should (theoretically) respond to all the methods that `NSValue` does. I could imagine it being quirky though. – Kurt Revis Jul 16 '12 at 02:45
  • Also, you can do this (for example): `[ NSNumber numberWithInt:(int){ *(int*)value } ]` which saves you some typing... – nielsbot Jul 16 '12 at 02:45
  • 1
    ah.. i just keep going... if you wanted to be "clever", you could even replace `+[ NSValue valueWithBytes:objCType: ]` using `class_replaceMethod()` with your own implementation that returns `NSNumber` (using the code above) for number types. Not sure that's good practice though :) – nielsbot Jul 16 '12 at 02:48
  • @nielsbot not to mention that memcpy has a biiiigg overhead... –  Jul 16 '12 at 03:04
  • @Kurt: That was my thought too, but for some reason that I haven't been able to puzzle out yet, `-[NSNumber initWithBytes:objCType:]` still returns an `NSValue`, not an `NSNumber`, so asking it later for its (e.g.) `intValue` crashes. – jscs Jul 16 '12 at 04:43
  • actually--I think memcpy and bcopy are aliased to fast compiler-supplied versions at compile time, and in fact may be very optimized for small copies... For example, see http://stackoverflow.com/a/4545241/210171 – nielsbot Jul 16 '12 at 05:20
  • @JoshCaswell I guess their init routine has `result = [ NSValue alloc ]...` instead of `result = [ [ self class ] alloc ]` :) – nielsbot Jul 16 '12 at 05:21
  • @nielsbot thanks for saving me from this bug. I tried `(int){(int*)value}` instead of memcpy. looks cleaner but I run into an memory alignment issue (exception `EXC_ARM_DA_ALIGN`) described [on this blog](http://www.splinter.com.au/what-do-do-with-excarmdaalign-on-an-iphone-ap/) – weezor Jul 19 '12 at 02:04
  • Well I suppose there's a possibility you could try to keep your original data aligned, but sounds like a lot of work and possible something you don't have control over... so yes, memcpy.. thanks for the heads up however. – nielsbot Jul 19 '12 at 07:04

1 Answers1

2

Here is my solution to the problem, only has specialization for floating-point values (seeing as they are weird!)

NSValue *safeValueForKVC(const void *input, const char *type)
{
    const char numericEncodings[] = { 
        'c',
        'i', 
        's', 
        'l', 
        'q', 
        'C', 
        'I',
        'S',
        'L',
        'Q',
        'f',
        'd',
    };
    const size_t sizeEncodings[] = {
        sizeof(char),
        sizeof(int),
        sizeof(short),
        sizeof(long),
        sizeof(long long),
        sizeof(unsigned char),
        sizeof(unsigned int),
        sizeof(unsigned short),
        sizeof(unsigned long),
        sizeof(unsigned long long),
        sizeof(float),
        sizeof(double),
    };

    int typeLen = strlen(type);

    if (typeLen == 1)
    {
        for (int i = 0; i < sizeof(numericEncodings); i++)
        {
            if (type[0] == numericEncodings[i])
            {
                // we have a numeric type, now do something with it
                if (i == 10)
                {
                    // floating-point value
                    float fValue = 0;

                    memcpy(&fValue, input, sizeEncodings[i]);

                    return [NSNumber numberWithFloat:fValue];
                }
                if (i == 11)
                {
                    // double value
                    double dValue = 0;

                    memcpy(&dValue, input, sizeEncodings[i]);

                    return [NSNumber numberWithDouble:dValue];
                }

                // standard numeric value, simply padding with false bits should work for any reasonable integer represetntation
                long long value = 0;
                memcpy(&value, input, sizeEncodings[i]);

                return [NSNumber numberWithLongLong:value];
            }
        }
    }

    return [[NSValue alloc] initWithBytes:input objCType:type];
}
Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201