Objective-C内存布局

对象(object)即一块内存,本文要探讨的是一个Objective-C对象在内存的布局(layout)问题,一个类(class)如果不需要从NSObject继承其某些特定的行为是不用继承NSObject的,这里我将讨论限制在继承了NSObject的类的对象范围内。
首先来看一下,NSObject的定义:

@interface NSObject  {
     Class    isa;
 } 

在Objective-C中,@interface关键字可以看着是C语言中的struct关键字的别名,当然他还会有一些其它功能,比如说让编译器知道@interface后后面的是一个Objective-C的类的名字等。但就我们研究其内存布局来说,我们简单地将其替换为struct,并将protocal定义去掉。因此,NSObject的定义就是样:

 struct NSObject{
   Class isa;
 }

那个这个Class又是什么呢?在objc.h中我们发现其仅仅是一个结构(struct)指针的typedef定义:

typedefstruct objc_class *Class;

因此,NSObject的定义就像这个样子:

1 struct NSObject{
2   objc_class *isa
3 }

isa就是“is a”,对于所有继承了NSObject的类其对象也都有一个isa指针。这个isa指针指向的东西(先这样称呼它吧)就是关于这个对象所属的类的定义。

刨根问底是我们程序员的天性:那object_class的定义是什么呢?由于水果公司现在将这个定义隐藏起来了,不过我依然有办法,用XCode随便建一个工程,在某个变量定义处打个debug断点,然后通过XCode的GUI或者用gdb的p命令查看其结构,这里我使用gdb打印一个UINavigationController变量,我们看到只是一个指针而已:

1 (gdb) p dialUNC
2 
3 $1 = (UINavigationController *) 0x8e8be80

对指针解引用再打印,我们发现里面有很多很多东西,大致如下(由于gdb打印出来内容太多,省略号表示省略了一些内容):

 1 (gdb) p *dialUNC
 2 $1 = {
 3    = {
 4      = {
 5        = {
 6         isa = 0x1bebc1c
 7       }, }, 
 8 members of UIViewController: 
 9     _view = 0xd5dab60, 
10     _tabBarItem = 0x0, 
11     _navigationItem = 0x0, 
12     _toolbarItems = 0x0, 
13     _title = 0x0, 
14     _nibName = 0x0, 
15     ......(此处省略若干成员,课蜜黄蜂注)
16   }, 
17 members of UINavigationController: 
18   _containerView = 0xd5dab60, 
19   _navigationBar = 0xd5dad40, 
20   _navigationBarClass = 0x1beb4d8, 
21   _toolbar = 0x0, 
22   _navigationTransitionView = 0xd5d2f10, 
23   _currentScrollContentInsetDelta = {
24     top = 0, 
25     left = 0, 
26     bottom = 0, 
27     right = 0
28   }, 
29   _previousScrollContentInsetDelta = {
30     top = 0, 
31     left = 0, 
32     bottom = 0, 
33     right = 0
34   }, 
35   ......(此处省略若干成员,课蜜黄蜂注) 
36   }
37 }

注意gdb打印结果中的黑体字,从中我们可以看到,UINavigationController内存中先是存放了父类的实例变量再存放子类的实例变量。最前面的那个isa指针就是在NSObject中所定义的。由于Objective-C中没有多继承,因此其内存布局还是很简单的,就是:最前面有个isa指针,然后父类的实例变量存放在子类的成员变量之前,so easy!!!但还有一个问题,我们很好奇,这个isa是什么呢?对它解引用再打印内容大致如下:

1 (gdb) p *dialUNC->isa
 2 $2 = {
 3     isa = 0x1bebc30, 
 4     super_class = 0x1bebba4, 
 5     name = 0xd5dd8d0 "?", 
 6     version = 45024840, 
 7     info = 223886032, 
 8     instance_size = 43102048, 
 9     ivars = 0x1bebb7c, 
10     methodLists = 0xd5dab10, 
11     cache = 0x2af0648, 
12     protocols = 0xd584050
13 }

这就是一个Class或者说objc_class结构在内存中的样子。其实在Objective-C2.0之前这个结构的定义是暴露给用户的,但在Objective-C2.0中,水果公司将它隐藏起来了。经过在网上的查找,发现在Objective-C2.0之前其定义大致如下:

1 struct objc_class {
 2     Class isa;
 3     
 4     Class super_class;
 5     
 6     const char *name;
 7     
 8     long version;
 9     long info;
10     
11     long instance_size;
12     struct objc_ivar_list *ivars;
13     struct objc_method_list **methodLists; 
14     
15     struct objc_cache *cache;
16     struct objc_protocol_list *protocols;   
17 }

因此简单地说,一个objc_class对象包括一个类的:父类定义(super_class), 变量列表,方法列表,还有实现了哪些协议(Protocal)等等。
"等一下",有人要喊了,"我们刚才在说一个对象里面有一个isa指针,这个指针的定义是objc_class,肿么这个objc-class中还有一个isa?"

在这里有必要跟大家啰嗦一大段文字了:在Objective-C中任何的类定义都是对象。即在程序启动的时候任何类定义都对应于一块内存。在编译的时候,编译器会给每一个类生成一个且只生成一个”描述其定义的对象”,也就是水果公司说的类对象(class object),他是一个单例(singleton), 而我们在C++等语言中所谓的对象,叫做实例对象(instance object)。对于实例对象我们不难理解,但类对象(class object)是干什么吃的呢?我们知道Objective-C是门很动态的语言,因此程序里的所有实例对象(instace objec)都是在运行时由Objective-C的运行时库生成的,而这个类对象(class object)就是运行时库用来创建实例对象(instance object)的依据。

让我们来理一下,到目前为止,我们知道了:任何直接或间接继承了NSObject的类,它的实例对象(instacne objec)中都有一个isa指针,指向它的类对象(class object)。这个类对象(class object)中存储了关于这个实例对象(instace object)所属的类的定义的一切:包括变量,方法,遵守的协议等等。

再回到之前的问题,肿么这个实例对象(instance object)的isa指针指向的类对象(class object)里面还有一个isa呢?

这个类对象(class objec)的isa指向的依然是一个objc-class,它就是“元类对象”(metaclass object),它和类对象(class object)的关系是这样的: 类对象(class object)中包含了类的实例变量,实例方法的定义,而元类对象(metaclass object)中包括了类的类方法(也就是C++中的静态方法)的定义。类对象和元类对象中水果公司当然还会包含一些其它的东西,以后也可能添加其它的内容,但对于我们了解其内存布局来说,只需要记住:类对象存的是关于实例对象的信息(变量,实例方法等),而元类对象(metaclass object)中存储的是关于类的信息(类的版本,名字,类方法等)。要注意的是,类对象(class object)和元类对象(metaclass object)的定义都是objc_class结构,其不同仅仅是在用途上,比如其中的方法列表在类对象(instance object)中保存的是实例方法(instance method),而在元类对象(metaclass object)中则保存的是类方法(class method)。关于元类对象水果官方文档" The Objective-‐C Programming Language "P29页顶部描述如下:

那我们来看一个例子。下面我以一个有4层继承关系的类的实例变量的内存布局为例。继承关系如下:
Objective-C内存布局_第1张图片
r_Screen Shot 2011-12-12 at 7.30.01 PM.jpg

通过打印D3类的一个实例变量并将那些isa,super_class的地地址记录下来整理得到的关系如下图:


Objective-C内存布局_第2张图片
r_Screen Shot 2011-12-13 at 9.42.39 PM.png

在这里对上图进行一下解释: 矩形表示对象(object),即一块内存;箭头表示指针,isa即isa指针,super表示super_class指针,这些指针是箭头尾部对象(object)的成员变量,除了“D3实例对象”(最左边的对象),其它对象都是在程序一启动就创建在在内存中的了而且都是单例(singleton),类对象(class object)和元类对象(metaclass object)只是用途不一样,其定义都为objc_class结构。

D3对象的内存布局为:从前往后为isa,D1的实例变量,D2的实例变量,D3的实例变量。而isa指针指向的内容就是上图中的“D3类对象”。对于上图,任何类C如果直接或间接继承NSObject 或者其就是NSObject,则有如下结论:

  1. 类C的类对象(class object)的super_class都指向了类C父类的类对象(class object), NSObject的类对像的super_class指向0x0
  2. 类C的类对象(class object)的isa指针都指向他的元类对象(metaclass object)
  3. 类C的元类对象(metaclass object)的super_class指针指向父类的元类对象(metaclass object), 例外:NSObject的元类对象(metaclass object)的super_class指向NSObject的类对象(class object).
  4. 类C的元类对象(metaclass object)的isa指针指都指向NSObject的元类对象(metaclass object)

NSObject的实例对象(虽然它没有实例变量和实例方法但这个对象仍然存在)其super_class指向地址0x0,因为NSObject没有父类, 这满足上面的结论1。

SObject的实例对象的isa指向了NSObject的元类对象(metaclass object),这满足上面结论2。

NSObject的元类对象(metaclass object)指向了自己,这也满足上面结论4。

但NSObject的元类对象(metaclass object)的super_class指向了NSObject的类对象(class object),我没有看出什么规律可言或者苹果为什么要这样做,我只能说“Apple just do this, I don't know why”(如果有人知道,麻烦告诉我一下,多谢)。我认为水果的工程师们只是简单地又将它指向NSObject的类对象(class object),其实我认为这个super_class指针赋0x0也未尝不可(这样就满足上面的结论3, 因为NSObject没有父类,所以它的metaclass object的super_class指向0x0,我觉得这样更统一。当然这只是我的yy罢了)。

你可能感兴趣的:(Objective-C内存布局)