Type Encodings

Mattt Thompson撰写、 Ricky Tan翻译、 发布于

数字电台数学命理象形文字流浪汉码,找到看似平常的东西中隐藏的意思真是令人着迷。即使它们中隐藏的信息很少用到或者并不特别有趣,但正是那种寻找的快感激发着我们强烈的好奇心。

在这种精神下,本周的 NSHipster 我们来看看 Objective-C Type Encodings


上一周,在讨论 NSValue 时提到了 +valueWithBytes:objCType:,它的第二个参数需要用 Objective-C 的编译器指令 @encode() 来创建。

@encode@编译器指令 之一,返回一个给定类型编码为一种内部表示的字符串(例如,@encode(int)i),类似于 ANSI C 的 typeof 操作。苹果的 Objective-C 运行时库内部利用类型编码帮助加快消息分发。

这里有一个所有不同的 Objective-C 类型编码的概要:

Objective-C Type Encodings
编码 意义
c A char
i An int
s A short
l A longl is treated as a 32-bit quantity on 64-bit programs.
q A long long
C An unsigned char
I An unsigned int
S An unsigned short
L An unsigned long
Q An unsigned long long
f A float
d A double
B A C++ bool or a C99 _Bool
v A void
* A character string (char *)
@ An object (whether statically typed or typed id)
# A class object (Class)
: A method selector (SEL)
[array type] An array
{name=type...} A structure
(name=type...) A union
bnum A bit field of num bits
^type A pointer to type
? An unknown type (among other things, this code is used for function pointers)

当然,用图表很不错,但是用代码实践更好:

NSLog(@"int        : %s", @encode(int));
NSLog(@"float      : %s", @encode(float));
NSLog(@"float *    : %s", @encode(float*));
NSLog(@"char       : %s", @encode(char));
NSLog(@"char *     : %s", @encode(char *));
NSLog(@"BOOL       : %s", @encode(BOOL));
NSLog(@"void       : %s", @encode(void));
NSLog(@"void *     : %s", @encode(void *));

NSLog(@"NSObject * : %s", @encode(NSObject *));
NSLog(@"NSObject   : %s", @encode(NSObject));
NSLog(@"[NSObject] : %s", @encode(typeof([NSObject class])));
NSLog(@"NSError ** : %s", @encode(typeof(NSError **)));

int intArray[5] = {1, 2, 3, 4, 5};
NSLog(@"int[]      : %s", @encode(typeof(intArray)));

float floatArray[3] = {0.1f, 0.2f, 0.3f};
NSLog(@"float[]    : %s", @encode(typeof(floatArray)));

typedef struct _struct {
    short a;
    long long b;
    unsigned long long c;
} Struct;
NSLog(@"struct     : %s", @encode(typeof(Struct)));

结果:

类型 编码
int i
float f
float * ^f
char c
char * *
BOOL c
void v
void * ^v
NSObject * @
NSObject #
[NSObject] {NSObject=#}
NSError ** ^@
int[] [5i]
float[] [3f]
struct {_struct=sqQ}

这里有一些特别需要注意的:

  • 指针的标准编码是加一个前置的 ^,而 char * 拥有自己的编码 *。这在概念上是很好理解的,因为 C 的字符串被认为是一个实体,而不是指针。
  • BOOLc,而不是某些人以为的 i。原因是 charint 小,且在 80 年代 Objective-C 最开始设计的时候,每一个 bit 位都比今天的要值钱(就像美元一样)。BOOL 更确切地说是 signed char (即使设置了 -funsigned-char 参数),以在不同编译器之间保持一致,因为 char 可以是 signed 或者 unsigned
  • 直接传入 NSObject 将产生 #。但是传入 [NSObject class] 产生一个名为 NSObject 只有一个类字段的结构体。很明显,那就是 isa 字段,所有的 NSObject 实例都用它来表示自己的类型。

方法编码

如苹果的 "Objective-C Runtime Programming Guide" 中所提到的,有一大把内部使用的类型编码无法用 @encode() 返回。

以下是协议中声明的方法的类型修饰符:

Objective-C Method Encodings
编码 意义
r const
n in
N inout
o out
O bycopy
R byref
V oneway

对于那些熟悉 NSDistantObject 的人,你无疑会认出这些是 Distributed Objects 的残留。

尽管 DO (Distributed Objects) 在 iOS 时代已经不那么时髦了,它仍是用于 Cocoa 应用程序进程间通信的协议————甚至用于网络上的不同机器之间。在这些约束下,上下文里附加的内容就带来了很多好处。

例如,分页式的对象消息的参数默认是用代理传递的。在那些没必要用到低效的代理的情况下,增加一个 bycopy 修饰符以保证发送了一份完整的拷贝。同样,默认情况下,带用 inout 的参数表明它在发消息时对象即可传入又可传出。将参数特别标注为 inout,程序将避免一些来回的开销。


我们从对 Objective-C 的类型编码的全新理解上能得到什么呢? 不瞒您说,其实没多少(除非你在做一些疯狂的元编程)。

但是就如我们最开始所说的,在追求破译密文的过程中要用到不少智慧。

看看类型编码为我们展现的有关 Objective-C 内部的细节,这本身就是一种高尚的追求。如果刨根问到底的话,我们需要了解一下 Distributed Objects 神秘的历史以及那 至今仍然存在 的复杂的参数修饰符。