从 数字电台 和 数学命理 到 象形文字 和 流浪汉码,找到看似平常的东西中隐藏的意思真是令人着迷。即使它们中隐藏的信息很少用到或者并不特别有趣,但正是那种寻找的快感激发着我们强烈的好奇心。
在这种精神下,本周的 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 中国大陆」许可协议授权。