从 数字电台 和 数学命理 到 象形文字 和 流浪汉码,找到看似平常的东西中隐藏的意思真是令人着迷。即使它们中隐藏的信息很少用到或者并不特别有趣,但正是那种寻找的快感激发着我们强烈的好奇心。
在这种精神下,本周的 NSHipster 我们来看看 Objective-C Type Encodings。
上一周,在讨论 NSValue
时提到了 +valueWithBytes:objCType:
,它的第二个参数需要用 Objective-C 的编译器指令 @encode()
来创建。
@encode
,@
编译器指令 之一,返回一个给定类型编码为一种内部表示的字符串(例如,@encode(int)
→ i
),类似于 ANSI C 的 typeof
操作。苹果的 Objective-C 运行时库内部利用类型编码帮助加快消息分发。
这里有一个所有不同的 Objective-C 类型编码的概要:
编码 | 意义 |
---|---|
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 的字符串被认为是一个实体,而不是指针。BOOL
是 c
,而不是某些人以为的 i
。原因是 char
比 int
小,且在 80 年代 Objective-C 最开始设计的时候,每一个 bit 位都比今天的要值钱(就像美元一样)。BOOL
更确切地说是 signed char
(即使设置了 -funsigned-char
参数),以在不同编译器之间保持一致,因为 char
可以是 signed
或者 unsigned
。NSObject
将产生 #
。但是传入 [NSObject class]
产生一个名为 NSObject
只有一个类字段的结构体。很明显,那就是 isa
字段,所有的 NSObject
实例都用它来表示自己的类型。如苹果的 “Objective-C Runtime Programming Guide” 中所提到的,有一大把内部使用的类型编码无法用 @encode()
返回。
以下是协议中声明的方法的类型修饰符:
编码 | 意义 |
---|---|
r | const |
n | in |
N | inout |
o | out |
O | bycopy |
R | byref |
V | oneway |
对于那些熟悉 NSDistantObject 的人,你无疑会认出这些是 Distributed Objects 的残留。
尽管 DO (Distributed Objects) 在 iOS 时代已经不那么时髦了,它仍是用于 Cocoa 应用程序进程间通信的协议————甚至用于网络上的不同机器之间。在这些约束下,上下文里附加的内容就带来了很多好处。
例如,分页式的对象消息的参数默认是用代理传递的。在那些没必要用到低效的代理的情况下,增加一个 bycopy
修饰符以保证发送了一份完整的拷贝。同样,默认情况下,带用 inout
的参数表明它在发消息时对象即可传入又可传出。将参数特别标注为 in
或 out
,程序将避免一些来回的开销。
我们从对 Objective-C 的类型编码的全新理解上能得到什么呢? 不瞒您说,其实没多少(除非你在做一些疯狂的元编程)。
但是就如我们最开始所说的,在追求破译密文的过程中要用到不少智慧。
看看类型编码为我们展现的有关 Objective-C 内部的细节,这本身就是一种高尚的追求。如果刨根问到底的话,我们需要了解一下 Distributed Objects 神秘的历史以及那 至今仍然存在 的复杂的参数修饰符。
除非另有声明,本文采用知识共享「署名-非商业性使用 3.0 中国大陆」许可协议授权。