NSValueMattt Ricky Tan 🚩🌱

在电影 eponymous 2012 biopic 中出演亚伯拉罕·林肯一角时,丹尼尔·戴 - 刘易斯坚决要求每次进出剧组时要通过一个标有“时间机器”字样的冷库箱子。真实的故事.

当戴 - 刘易斯先生要进行方法演技时那当然会不择手段,人们对用一个神奇的硬纸板箱连接了现代世界和历史世界这件荒唐的事无不感到惊讶。

然而,为作一个 Objective-C 的程序员,这 正好 是我们正在做的。

……好吧,不是 正好 ,但这里也需要有一个箱子。

就像在 NSHipster 中 一而再 再而三 地提到的那样,是什么让 Objective-C 如此的神奇?正是将过时的,面向过程的 C 的世界与受 Smalltalk 影响的现代的面向对象的世界兼并的方法。如果能恰到好处地兼并,两个世界的对立关系就可以被用来熟练地开发语义上丰富的软件,而不牺牲性能。但是在新与旧的鸿沟之间架桥的却是乌烟瘴气的强制类型转换、无缝转换和包装。

打包就是用面向对象的容器来封装标量(intdoubleBOOL 等)和值类型(structenum)的过程,主要用于将那些值保存到面向对象的集合中——如数组和字典。

NSNumber 常用于包装标量,但是在基础类库中,轻量级的包装冠军是 NSValue


NSValue 是用于承载单一的 C 或 Objective-C 数据值的容器。它能承载标量和值类型,也能用于指针和对象 ID。

如何打包固然是个枯燥无味的主题,但仍有两个方法尤其值得注意:+valueWithBytes:objCType:,使用 NSValue 的入门级方法;+valueWithNonretainedObject:,相对较少人知道却十分有用的方法。

valueWithBytes:objCType:

+valueWithBytes:objCType: 创建并返回一个包含给定值的 NSValue 对象,该值会被解释为一个给定的 Objective-C 类型。

  • value: NSValue 对象的值。
  • type: 给定值的对应的 Objective-C 类型。type 需要用 Objective-C 的编译器指令 @encode() 来创建,而不应该用硬编码的 C 语言字符串。

@encode 在文章 无数个 @ 编译器指令的摘要 中有讨论过:

@encode(): 返回一种类型的类型编码。这个类型值能用做 NSCoder -encodeValueOfObjCType:at 的第一个参数。

关于类型编码的主题可以写一篇很好的文章,但其主要内容是它用于将一种类型的结构转换为精练的,可读的表示。例如,

typedef struct example {
  id   anObject;
  char *aString;
  int  anInt;
} Example;

有如下编码:

{example=@*i}

NSValue 用类型编码创建必要的数据结构以在内部表示这些值。整洁!

valueWithNonretainedObject:

+valueWithNonretainedObject: 创建并返回一个包含给定对象的 NSValue。

anObject: 新对象的值。

如果你已经知道 valueWithNonretainedObject,你应该会微笑点头。如果你不知道,你可能会目瞪口呆,即使没有也快了。

简要地说,valueWithNonretainedObject: 允许一个对象添加到集合中去,而不需要服从 <NSCopying> 协议。

偶尔会出现一些情况需要用到无法直接添加到 NSArrayNSDictonary 中的对象。如果不知道 valueWithNonretainedObject:,这会让你全盘皆输————特别是你又是个 Objective-C 新手的话。

但现在你知道了。你不会在行人注目之下感到窒息。你不再需要摸索着从 NSPointerArrayNSMapTable 寻找答案。今天会是全新的一天。


在打开了 NSValue 的所有智慧之后,现在你可以从容应对面向过程和面向对象、C 与 Smalltalk 之间的残酷划分了。因为一个神奇的箱子,所有事情都变得简单。

除非另有声明,本文采用知识共享「署名-非商业性使用 3.0 中国大陆」许可协议授权。