How to convert NSValue to NSData and back?

生来就可爱ヽ(ⅴ<●) 提交于 2020-01-10 10:14:35

问题


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.


回答1:


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.




回答2:


Since NSValue adopts the NSCoding protocol, you can use the NSCoder subclasses, NSKeyedArchiver and NSKeyedUnarchiver:

- (NSData *)dataWithValue:(NSValue *)value {
  return [NSKeyedArchiver archivedDataWithRootObject:value];
}

- (NSValue *)valueWithData:(NSData *)data {
  return (NSValue *)[NSKeyedUnarchiver unarchiveObjectWithData:data];
}

If NSKeyedArchiver and NSKeyedUnarchiver can't be used (for resulting data size issues), then you would have to find the size of the contained value yourself:

- (NSData *)dataWithValue:(NSValue *)value {
  // Can use NSGetSizeAndAlignment() instead. See LearnCocos2D's answer.
  if (type[0] == '{') {
    // Use the various NSValue struct value methods to detect the type.
  } else if (type[0] == 'c') {
    size = sizeof(char);
  } else if (type[0] == 'i') {
    size = sizeof(int);     
  } // etc for all/most of the values in the table linked below [1];

  void *bytes = malloc(size);
  [value getValue:bytes];
  return [NSData dataWithBytes:bytes length:size];
}

[1]: Objective-C Type Encodings




回答3:


You can use NSCoder (like NSKeyedArchiver) to archive the data, then send the resulting NSData. On the other side you can unarchive it. There may be some caveats with this (for example http://www.cocoabuilder.com/archive/cocoa/82344-nskeyedarchiver-and-nsvalue.html) esepcially if you are wrapping structs and other non-archiving stuff in NSValue.



来源:https://stackoverflow.com/questions/8447380/how-to-convert-nsvalue-to-nsdata-and-back

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