Objective-C内存布局

 在个人理解来讲: 对象(object)即一块内存,本文要探讨的是一个Objective-C对象在内存的布局(layout)问题,水果的官方文档有说,一个类(class)若是不须要从NSObject继承其某些特定的行为是不用继承NSObject的,这里我将讨论限制在继承了NSObject的类的对象范围内。html

首先来看一下,NSObject的定义:程序员

1 @interface NSObject <NSObject> {
2 Class isa;
3 }

(因为咱们讨论的是内存布局,所以将其方法的定义撇开)objective-c

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

1 struct NSObject{
2   Class isa;
3 }

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

1 typedefstruct objc_class *Class;

 所以,NSObject的定义就像这个样子:this

1 struct NSObject{
2   objc_class *isa
3 }

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

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

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

对指针解引用再打印,咱们发现里面有不少不少东西,大体以下(因为gdb打印出来内容太多,省略号表示省略了一些内容):指针

复制代码
 1 (gdb) p *dialUNC
2 $1 = {
3 <UIViewController> = {
4 <UIResponder> = {
5 <NSObject> = {
6 isa = 0x1bebc1c
7 }, <No data fields>},
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是什么呢?对它解引用再打印内容大体以下:code

复制代码
 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页顶部描述以下:

  Note: The compiler also builds a metaclass object for each class. It describes the class object just as the class object describes instances of the class. But while you can send messages to instances and to the class object, the metaclass object is used only internally by the runtime system. 

  这一大段文字好像有点绕,那咱们来看一个例子。下面我以一个有4层继承关系的类的实例变量的内存布局为例。继承关系以下:

类继承图 

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

 对象内存关系图

  在这里对上图进行一下解释: 矩形表示对象(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。

  NSObject的实例对象的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罢了)。

      到此结束,欢迎你们拍砖。

      参考文献: 1. Apple官方文档" The Objective-‐C Programming Language "

       2. http://algorithm.com.au/downloads/talks/objective-c-internals/objective-c-internals.pdf

转:http://www.cnblogs.com/csutanyu/archive/2011/12/12/Objective-C_memory_layout.html

 

相关文章
相关标签/搜索