Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert NSValue to NSData and back?

A have a number of NSValue (obtained via KVC valueForKey) that I need to append to an NSData object in order to send it over the network using Game Center. Obviously I will also need to convert the NSValue back from NSData for more KVC (setValue:forKey:).

I don't know the exact type of each NSValue, so I'm limited to using the interfaces provided by NSValue and NSData. I should mention that the NSValue are never pointer types.

I'm surprised that there's no [NSData dataWithValue:value] and neither something like [value dataWithEncoding:] or similar. But maybe I'm blind and not seeing the obvious choice. I thought about using getValue: to copy the value into a void* buffer, but how am I supposed to determine the buffer length other than by using objCType and comparing that with all possible types?

How should I go about this?

NOTE: NSKeyedArchiver is out of the question because it is terribly inefficient. A 4 Byte float is archived to a 142 Bytes NSData, a 8 Byte CGPoint uses 260 Bytes when archived to NSData. Keeping the amount of data sent to a minimum is crucial to what I'm doing.

like image 385
LearnCocos2D Avatar asked Dec 09 '11 15:12

LearnCocos2D


1 Answers

Martin Gordon's answer is getting close, but fortunately you don't have to manually parse the objCType string. There's a function that does that: NSGetSizeAndAlignment. From that you get the size/length of the value obtained from getValue:. This question lead me to the solution.

I ended up creating a category for NSData that allows me to create NSData from NSNumber or NSValue objects:

@interface NSData (GameKitCategory)
+(NSData*) dataWithValue:(NSValue*)value;
+(NSData*) dataWithNumber:(NSNumber*)number;
@end

@implementation NSData (GameKitCategory)
+(NSData*) dataWithValue:(NSValue*)value
{
    NSUInteger size;
    const char* encoding = [value objCType];
    NSGetSizeAndAlignment(encoding, &size, NULL);

    void* ptr = malloc(size);
    [value getValue:ptr];
    NSData* data = [NSData dataWithBytes:ptr length:size];
    free(ptr);

    return data;
}

+(NSData*) dataWithNumber:(NSNumber*)number
{
    return [NSData dataWithValue:(NSValue*)number];
}
@end

I also add a small header before this NSValue/NSNumber data that allows me to decode which property the data is for and how many bytes are in the data section. With that I can restore the value to the remote property.

like image 172
LearnCocos2D Avatar answered Sep 18 '22 07:09

LearnCocos2D