Deep Inside Objective-C: the Application Binary Interface about Classes and Class Instances

很多 Cocoa Programming 的书里都会提到 Objective-C 和 C++ 的一个重要
区别 -- C++ 里每个 class instance 是一个 object,而一个 class 只是
一个抽象的概念,而不是内存中的一块数据;而 Objective-C 的 class 在内存中
会有一块数据表示,就像每个 class instance 一样,Objective-C 中的 class 本
身也是一个 object。

Xian Zhang提醒我:Java和Objective-C一样,每个class都有一块内存数据结构,所以Java也支持class Introspection。随后我在 Cocoa Programming Developer Handbook 这本书里还找到一个八卦:

While many people think of Java as being close to C++, this is not really true. The syntax is vaguely like C++, but semantically it inherits a lot from Objective-C. This is not surprising, since a lot of the people who worked on Java came from NeXT or were involved with OpenStep at Sun.

原来搞Java的人里很多就是之前搞Objective-C的人。Steve Jobs的NeXT和Sun合作搞OpenStep影响了Java的设计。

那么一个 class object 里包含哪些信息,以及这些信息是如何支持 Objective-C
程序的运行的呢?

要回答这样的问题,我们需要看看 Objective-C compiler 的源码。最常用的
Objective-C 编译器是 GNU 的 GCC —— 在 Linux 和 Cygwin 下都有实现。在 Mac
OS X 下,GCC 是 XCode 开发环境的重要组成部件。(虽然 XCode 4 开始推荐使
用另一个编译器 Clang,但是 Clang 的用法和 GCC 基本完全一样。)

我们想看看 GCC 的源码,找找其中对 class object 的定义。如果你使用的是
Mac OS X + XCode 4,这段源码在:

/usr/include/objc/runtiem.h

如果你使用的是 Linux,可以通过这个命令来找到源码文件:

locate objc.h

比如在我的 32-bit Ubuntu Linux 11.04 系统上,这段源码在:

/usr/lib/i386-linux-gnu/gcc/i686-linux/gnu/4.5/include/objc/objc.h

打开上述源码文件,可以看到很多以objc_开头的类型、函数和变量名。
这些都是 Objective-C 的内部细节。其中描述每个 class 对应的数据结构的,是
C struct objc_class。在Ubuntu Linux + GCC 的环境里,这个
struct 的定义如下:

struct objc_class {
  struct objc_class*         class_pointer;
  struct objc_class*         super_class;
  const char*                name;
  long                       version;
  unsigned long              info;
  long                       instance_size;
  struct objc_ivar_list*     ivars;
  struct objc_method_list*   methods;
  struct sarray *            dtable;
  struct objc_class*         subclass_list;
  struct objc_class*         sibling_class;
  struct objc_protocol_list* protocols;
  void*                      gc_object_type;
};

看看这个结构,我们就能理解很多 Objective-C 的语法规则是如何实现的了:

One Class One Parent:上面代码中,name 指向一个
C字符串,就是 class 的名字; super_class 指向另一个 struct
objc_class
对象,这个对象表示的是当前 class 的父类。因为只有一个
super_class 指针,所以一个 class 只能有一个父类,而不像 C++ 中的multiple
inheritance 那样容许一个 class 有多个父类。

Instrospection: ivars 指向一个列
表,说明这个 class 中有哪些 instance variables;methods 指向一个
列表,其中每一项对应这个 class 中的一个 instance method。ivars
methods 的存在,容许 Objective-C 程序在运行时了解一个每个
class 的具体情况。这种能力被称为introspection。在不少介绍 Objective-C 的
书里,introspection 被称为 reflection -- 其实这是不对的 --
introspection 指的是对 ivars, methods 的读操
作,reflection 指的是读和写操作。在 Objective-C 里,我们不应该修改
ivarsmethods 的值。(虽然看了这篇帖子,你就知道可以修
改了,但是我们还是别太随便的把编译器辛辛苦苦建立起来的数据结构弄乱了。)

Message Routing:在 Objective-C 里,调用一个 class
instance 的某个 instance method,又被称为向这个 class instance 发送一个
message。Objective-C 的 messaing routining 的一条重要规则是:如果一个
class instance 不认识发来的 message (这个 class 的 methods列表中
没有对应的一项),那么 Objective-C的 runtime system 会去 parent class 的
methods 列表里找这个message。这里,找一个 class 的 parent class
是通过 super_class 指针实现的。

Class Methods: struct objc_class 中只有一个
methods 列表,其中是一个 class 的instance methods;那么这个 class 的
class methods 在哪里呢? 答案是在class_pointer 指向的那个
struct objc_class 对象(被称为meta-class) 的methods 列表
里。换句话说,一个 class 是用两个struct objc_class 对象来表示
的,其中一个描述 instance variables和 instance methods,另一个描述 class
methods。

上面的代码段是从 Linux 下的GCC环境里找到的。从 Mac OS X + XCode 4 环境里找到的代码如下:

struct objc_class {
  Class isa;
#if !__OBJC2__
  Class super_class                    OBJC2_UNAVAILABLE;
  const char *name                     OBJC2_UNAVAILABLE;
  long version                         OBJC2_UNAVAILABLE;
  long info                            OBJC2_UNAVAILABLE;
  long instance_size                   OBJC2_UNAVAILABLE;
  struct objc_ivar_list *ivars         OBJC2_UNAVAILABLE;
  struct objc_method_list**methodLists OBJC2_UNAVAILABLE;
  struct objc_cache *cache             OBJC2_UNAVAILABLE;
  struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

和 GCC 源码中的代码段不同的是,这里很多 fileds 只有在宏
__OBJC2__ 被定义的情况下才被定义。 这是因为苹果公司希望在
Objective-C 2.0 里尽量隐藏不必公开的实现细节,从而保证 ABI (Application
Binary Interface)的可扩展性。

值得注意的是:第一个 field(名为isa的)是不被隐藏的。它和 GCC 源码
版本中的 class_pointer 等价。这里可以注意到:Objective-C的 Class 类型,来
自于

typedef struct objc_class *Class;

要理解 Objective-C 的 ABI,还有一个 C struct 也很重要,这就是描述每个
class instance 的 C struct:

typedef struct objc_class *Class;
typedef struct objc_object {
    Class isa;
} *id;

id: 这里出现了每个 Objective-C 程
序员都很熟悉的类型, id,的定义-- id 实际上是一个指针,指向内
存中的块数据,这块数据最前面是一个指针(isa),指向一个描述这个
class instance 对应的 class 的那个struct objc_class 结构。

[NSObject class]: 这里实际上对 Objective-C 的每个 class
instance 的内存布局提出了一个要求: 每个 instance 的第一个 instance
variable 必须是一根指向 struct objc_class 对象的指针。这是怎么实现的呢?
-- 通常我们写 Objective-C 程序的时候,我们的base class 都是从 NSObject
派生出来的,而 NSObject 的第一个 instance variable 就是 Class
isa
:

@interface NSObject  {
    Class	isa;
}

当我们调用NSObject的class method的时候,class就是返回了isa。

基于上面的讨论,我们来写一个 Objective-C 程序,它定义一些 class, 生成一
些 instances,然后打印对这些 instances 和 class objects 的 introspection
的结果。它的最新版本在Google Code上,可以从SVN checkout:

svn checkout http://yiwang-learning-projects.googlecode.com/svn/trunk/objc-runtime/ deep_inside_objc

这个程序是在安装了 GNUstep 的 Linux 环境下写的。你可以修改之,使它可以在
Mac OS X 下编译和运行。

#import
#import
#import
#import

@interface ParentClass : NSObject {
  int count;
}
-(id)initWithCount:(int)c;
-(int)count;
+(id)createWithCount:(int)c;
@end

@implementation ParentClass
-(id)initWithCount:(int)c {
  count = c;
  return self;
}
-(int)count {
  return count;
}
+(id)createWithCount:(int)c {
  return [[ParentClass alloc] initWithCount:c];
}
@end

@interface MyClass : ParentClass {
  float real_count;
}
-(id)initWithCount:(int)c RealCount:(float)rc;
-(float)realCount;
+(id)createWithCount:(int)c RealCount:(float)rc;
@end

@implementation MyClass
-(id)initWithCount:(int)c RealCount:(float)rc {
  [super initWithCount:c];
  real_count = rc;
  return self;
}
-(float)realCount {
  return real_count;
}
+(id)createWithCount:(int)c RealCount:(float)rc {
  return [[MyClass alloc] initWithCount:c RealCount:rc];
}
@end

void print_objc_method_list(struct objc_method_list* ml) {
  int i;
  for (i = 0; i < ml->method_count; ++i) {
    printf("  %s\n", ml->method_list[i].method_types);
  }
  if (ml->method_next != NULL) {
    print_objc_method_list(ml->method_next);
  }
}

void print_objc_class(char* class_or_meta_class, struct objc_class* c) {
  printf("%s of %s {\n"
	 " instance size = %d\n"
	 " methods = {\n",
	 class_or_meta_class, c->name, c->instance_size);
  print_objc_method_list(c->methods);
  printf(" }\n}\n");
}

int main(void) {
  id myclass_obj = [MyClass createWithCount:5 RealCount:10.0];
  printf("myclass {\n count = %d\n real_count = %f\n}\n",
	 [myclass_obj count], [myclass_obj realCount]);
  print_objc_class("class", myclass_obj->class_pointer);
  print_objc_class("meta-class", myclass_obj->class_pointer->class_pointer);
  print_objc_class("class", myclass_obj->class_pointer->super_class);
  print_objc_class("meta-class", myclass_obj->class_pointer->super_class->class_pointer);
  print_objc_class("class", myclass_obj->class_pointer->super_class->super_class);
  return 0;
}

用如下方式调用 GCC 来编译这个程序:

gcc -g -O0  -o $@ deep_inside_objc.m -I/usr/include/GNUstep \
  -lgnustep-base -lobjc

程序运行时的输出结果大致如下:

myclass {
 count = 5
 real_count = 10.000000
}
class of MyClass {
 instance size = 12
 methods = {
  f8@0:4
  @16@0:4i8f12
 }
}
meta-class of MyClass {
 instance size = 52
 methods = {
  @16@0:4i8f12
 }
}
class of ParentClass {
 instance size = 8
 methods = {
  i8@0:4
  @12@0:4i8
 }
}
meta-class of ParentClass {
 instance size = 52
 methods = {
  @12@0:4i8
 }
}
class of NSObject {
 instance size = 4
 methods = {
  @8@0:4
  @8@0:4
  @12@0:4@8
  @8@0:4
  @8@0:4
  C12@0:4*8
  v20@0:4@8@12@16

结果中打印的 method type 看似密码,其实是对 method signature 的简单的
encoding。我们可以忽略其中的数字,只看其中符号,然后对照以下符号表(来自
GCC 头文件 /usr/lib/i386-linux-gnu/gcc/i686-linux-gnu/4.5.2/include/objc/objc-api.h)理解其
意思。

/* Filer types used to describe Ivars and Methods.  */
#define _C_ID       '@'
#define _C_CLASS    '#'
#define _C_SEL      ':'
#define _C_CHR      'c'
#define _C_UCHR     'C'
#define _C_SHT      's'
#define _C_USHT     'S'
#define _C_INT      'i'
#define _C_UINT     'I'
#define _C_LNG      'l'
#define _C_ULNG     'L'
#define _C_LNG_LNG  'q'
#define _C_ULNG_LNG 'Q'
#define _C_FLT      'f'
#define _C_DBL      'd'
#define _C_BFLD     'b'
#define _C_BOOL	    'B'
#define _C_VOID     'v'
#define _C_UNDEF    '?'
#define _C_PTR      '^'
#define _C_CHARPTR  '*'
#define _C_ATOM     '%'
#define _C_ARY_B    '['
#define _C_ARY_E    ']'
#define _C_UNION_B  '('
#define _C_UNION_E  ')'
#define _C_STRUCT_B '{'
#define _C_STRUCT_E '}'
#define _C_VECTOR   '!'
#define _C_COMPLEX   'j'

我们也可以在 GDB 里运行这个程序,更细致的看看 super_class
class_pointer 这些指针形成的链式结构。其结果大致如下图(点击下载
大图)。