Как преобразовать NSValue в NSData и обратно?

A имеет число NSValue (полученное через KVC valueForKey), которое мне нужно добавить к объекту NSData, чтобы отправить его по сети с помощью Game Center. Очевидно, мне также понадобится преобразовать NSValue назад из NSData для большего количества KVC (setValue:forKey:).

Я не знаю точного типа каждого NSValue, поэтому я ограничен использованием интерфейсов, предоставляемых NSValue и NSData. Следует отметить, что NSValue никогда не являются типами указателей.

Я удивлен, что там нет [NSData dataWithValue:value] и не что-то вроде [value dataWithEncoding:] или подобного. Но, возможно, я слепой и не вижу очевидного выбора. Я думал об использовании getValue: для копирования значения в буфер void*, но как я должен определять длину буфера, кроме как используя objCType, и сравнивая это со всеми возможными типами?

Как мне это сделать?

Примечание: NSKeyedArchiver не может быть и речи, потому что это ужасно неэффективно. Поля 4 байта архивируются до 142 байтов NSData, а 8 байтов CGPoint использует 260 байтов при архивировании на NSData. Сохранение количества данных, присланных до минимума, имеет решающее значение для того, что я делаю.

Ответ 1

Ответ Мартина Гордона приближается, но, к счастью, вам не нужно вручную разбирать строку objCType. Там есть функция, которая делает это: NSGetSizeAndAlignment. Из этого вы получите размер/длину значения, полученного от getValue:. Этот вопрос приведет меня к решению.

В итоге я создал категорию для NSData, которая позволяет мне создавать NSData из объектов NSNumber или NSValue:

@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

Я также добавляю небольшой заголовок перед данными NSValue/NSNumber, который позволяет мне декодировать, для какого свойства используются данные, и количества байтов в разделе данных. С этим я могу восстановить значение для свойства remote.

Ответ 2

Так как NSValue использует протокол NSCoding, вы можете использовать подклассы NSCoder, NSKeyedArchiver и NSKeyedUnarchiver:

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

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

Если NSKeyedArchiver и NSKeyedUnarchiver не могут использоваться (для возникающих проблем с размером данных), вам нужно будет самостоятельно найти размер содержащегося значения:

- (NSData *)dataWithValue:(NSValue *)value {
  // Can use NSGetSizeAndAlignment() instead. See LearnCocos2D 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 Кодировки типов

Ответ 3

Вы можете использовать NSCoder (например, NSKeyedArchiver) для архивации данных, а затем отправить полученные NSData. С другой стороны, вы можете его разблокировать. Могут быть некоторые предостережения с этим (например http://www.cocoabuilder.com/archive/cocoa/82344-nskeyedarchiver-and-nsvalue.html), если вы обертываете структуры и другие материалы, не связанные с архивированием, в NSValue.