【问题标题】:How to convert NSValue to NSData and back?如何将 NSValue 转换为 NSData 并返回?
【发布时间】:2012-01-16 20:34:06
【问题描述】:

A 有许多 NSValue(通过 KVC valueForKey 获得),我需要将其附加到 NSData 对象,以便使用 Game Center 通过网络发送。显然,我还需要将 NSValueNSData 转换回来以获得更多 KVC (setValue:forKey:)。

我不知道每个NSValue的具体类型,所以仅限于使用NSValueNSData提供的接口。我应该提到NSValue 永远不是指针类型。

我很惊讶没有[NSData dataWithValue:value],也没有类似[value dataWithEncoding:] 或类似的东西。但也许我是盲人,没有看到明显的选择。我考虑过使用getValue: 将值复制到void* 缓冲区中,但是除了使用objCType 并将其与所有可能的类型进行比较之外,我应该如何确定缓冲区长度?

我该怎么办?

注意: NSKeyedArchiver 是不可能的,因为它非常低效。 4 字节浮点数归档到 142 字节 NSData,8 字节 CGPoint 在归档到 NSData 时使用 260 字节。将发送的数据量保持在最低限度对我的工作至关重要。

【问题讨论】:

  • NSValue 支持 NSCoding,因此您可以使用 NSKeyedArchiver 或 NSArchiver 创建 NSData 表示。
  • 您可以随时添加一个类别,根据 objcType 变量将其转换为数据。

标签: objective-c nsdata key-value-coding nscoding


【解决方案1】:

Martin Gordon 的答案越来越接近,但幸运的是您不必手动解析 objCType 字符串。有一个函数可以做到这一点:NSGetSizeAndAlignment。从中您可以获得从 getValue: 获得的值的大小/长度。 This question 引导我找到解决方案。

我最终为 NSData 创建了一个类别,允许我从 NSNumber 或 NSValue 对象创建 NSData:

@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 数据之前添加了一个小标题,它允许我解码数据用于哪个属性以及数据部分中有多少字节。有了它,我可以将值恢复到远程属性。

【讨论】:

  • 我不能给你足够多的支持,这很痛苦!你认为这更像是一个 Objective-C 运行时的东西(比如 ivar_sizeOfObjcType 或其他东西)。
  • Steffen,当我使用别人的想法时,我喜欢承认。我拿了上面的代码并对其进行了按摩以满足我的感受。它包含在我在 GitHub 上的 DDGPreferences 项目中:github.com/adonoho/DDGPreferences>。感谢您公开探索这个想法。安德鲁
  • 从 NSData 回到 NSNumber 的反向是什么样子?
  • 您不应该需要它,因为您通常会将值本身编码为 NSData,而不是包装该值的 NSNumber。例如:stackoverflow.com/questions/9224712/…
【解决方案2】:

由于NSValue采用NSCoding协议,所以可以使用NSCoder子类NSKeyedArchiverNSKeyedUnarchiver

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

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

如果NSKeyedArchiverNSKeyedUnarchiver 不能使用(用于产生数据大小问题),那么您必须自己查找所包含值的大小:

- (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

【讨论】:

  • 恐怕 NSKeyedArchiver 在通过网络发送数据时效率非常低。一个简单的浮点(4 个字节)值被放大到 142 个字节,一个 CGPoint(8 个字节)在密钥归档到 NSData 时是 260 个字节。没有其他选择吗?
  • 感谢您的更新。我担心它会变成这样,这意味着比较 objCType 的字符串值。幸运的是,由于 NSGetSizeAndAlignment(),这不是必需的。我会用我的解决方案发布答案​​。
【解决方案3】:

您可以使用 NSCoder(如 NSKeyedArchiver)来归档数据,然后发送生成的 NSData。另一方面,您可以取消归档它。这可能有一些警告(例如http://www.cocoabuilder.com/archive/cocoa/82344-nskeyedarchiver-and-nsvalue.html),尤其是如果您在 NSValue 中包装结构和其他非归档内容。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-29
    • 1970-01-01
    • 1970-01-01
    • 2015-10-11
    • 2013-11-29
    • 2012-01-11
    • 1970-01-01
    相关资源
    最近更新 更多