Objective-C内存布局
在我的理解来说: 对象(object)即一块内存,本文要探讨的是一个Objective-C对象在内存的布局(layout)问题,水果的官方文档有说,一个类(class)如果不需要从NSObject继承其某些特定的行为是不用继承NSObject的,这里我将讨论限制在继承了NSObject的类的对象范围内。
首先来看一下,NSObject的定义:
1 @interface NSObject <NSObject> {
2 Class isa;
3 }
(由于我们讨论的是内存布局,因此将其方法的定义撇开)
在Objective-C中,@interface关键字可以看着是C语言中的struct关键字的别名,当然他还会有一些其它功能,比如说让编译器知道@interface后后面的是一个Objective-C的类的名字等。但就我们研究其内存布局来说,我们简单地将其替换为struct,并将protocal定义去掉。因此,NSObject的定义就是样:
1 struct NSObject{
2 Class isa;
3 }
那个这个Class又是什么呢?在objc.h中我们发现其仅仅是一个结构(struct)指针的typedef定义:
1 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 <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是什么呢?对它解引用再打印内容大致如下:

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

Objective-C内存布局的更多相关文章
- 图说C++对象模型:对象内存布局详解
0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看. 本文的结论都在VS2013上得到验证.不同的编译器在内存布局的细节上可能有 ...
- C++ 系列:内存布局
转载自http://www.cnblogs.com/skynet/archive/2011/03/07/1975479.html 为什么需要知道C/C++的内存布局和在哪可以可以找到想要的数据?知道内 ...
- C++类内存布局图(成员函数和成员变量分开讨论)
一.成员函数 成员函数可以被看作是类作用域的全局函数,不在对象分配的空间里,只有虚函数才会在类对象里有一个指针,存放虚函数的地址等相关信息. 成员函数的地址,编译期就已确定,并静态绑定或动态的绑定在对 ...
- 根据内存布局定位的一个fastdfs坑
在使用fastdfs时,编写数据上传代码时,遇到一个坑.最终根据指针对应的内存布局定位到一个其client API的一个坑,值得记录一下.具体是在 tracker_connect_server() 这 ...
- c++ 对象的内存布局
之前介绍过了普通对象比如系统自带的int等对象的对齐方式,在学习类型转换的时候遇到了自定义类型的继承体系中的downcast与upcast. 于是顺藤摸瓜,摸到了这里.发现还是 陈皓的博客里面写的最早 ...
- C++使用继承时子对象的内存布局
C++使用继承时子对象的内存布局 // */ // ]]> C++使用继承时子对象的内存布局 Table of Contents 1 示例程序 2 对象的内存布局 1 示例程序 class ...
- .NET对象的内存布局
每个虚拟机都有它自己的对象布局,本文我们将针对sscli源码和windbg调试器来查看不同类型的.net对象布局. 在.net虚拟机里,每个对象都需要保存这些信息: 对象的类型: 对象实例的成员属性( ...
- c/c++ 对象内存布局
一.对象内存查看工具 VS 编译器 CL 的一个编译选项可以查看 C++ 类的内存布局,非常有用.使用如下,从开始程序菜单找到 Visual Stdio 2012. 选择 VS 的命令行工具,按如下格 ...
- C++ Data Member内存布局
如果一个类只定义了类名,没定义任何方法和字段,如class A{};那么class A的每个实例占用1个字节的内存,编译器会会在这个其实例中安插一个char,以保证每个A实例在内存中有唯一的地址,如A ...
- C++中派生类对象的内存布局
主要从三个方面来讲: 1 单一继承 2 多重继承 3 虚拟继承 1 单一继承 (1)派生类完全拥有基类的内存布局,并保证其完整性. 派生类可以看作是完整的基类的Object再加上派生类自己的Objec ...
随机推荐
- InteliJ IDEA 简单使用:配置项目所需jdk
1:配置项目所需jdk: File->Project Structure 弹出如下界面: 首先选中SDKs,会出现下图界面:点击“+”标志弹出Add New SDK 然后选择JDK,会弹出路径框 ...
- NFS基础配置
需要安装的包: rpc-bind nfs-utils 修改配置文件 /etc/exports 配置 /tmp *(ro) 修改配置之后记得重启服务 sudo systemctl restart nfs ...
- java 内部类使用 .this 和 .new
如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和this,这样产生的引用自动地具有正确的类型,这一点在编译器就被知晓并受到检查,因此并没有运行时开销 //: innerclasses ...
- 003_Java笔记3:Eclipse添加jar包
本文以jedis包为例,演示Eclipse如何添加和使用jar包. 1 建立一个名为ImportJarDemo的Java Project.在该工程下建立一个libs的文件夹. 2 将下载的jedi ...
- [转] 实时监听input输入的变化(兼容主流浏览器)
遇到如此需求,首先想到的是change事件,但用过change的都知道只有在input失去焦点时才会触发,并不能满足实时监测的需求,比如监测用户输入字符数. 在经过查阅一番资料后,欣慰的发现firef ...
- 2018年东北农业大学春季校赛 C-wyh的商机
一天,你们wyh学长和你们zhl学长玩一个游戏,这个游戏规则是这样的 给你n个城市,保证这n个城市之间都只有一条道路可以到达. 有一件物品,在所有城市中都是一样的,但是由于各个城市的经济发展不同,导致 ...
- js变量和函数声明的提升(转)
原文:http://zha-zi.iteye.com/blog/2037026 下面的程序是什么结果? var foo = 1; function bar() { if (!foo) { var fo ...
- linux学习笔记-1.man_page
1.内部命令:echo 查看内部命令帮助:help echo 或者 man echo 2.外部命令:ls 查看外部命令帮助:ls --help 或者 man ls 或者 info ls 3.man文档 ...
- 【Vue实战之路】一、Vue-cli入门及Vue工程目录全解。
全面的Vue-cli学习,这一篇就够了! 一.下载 使用vue-cli前,需先安装node.js,node的安装就不赘述,不过在此需要注意: 1. node版本需在4.x以上,首推6.x以上版本(no ...
- HDU3439 Sequence
今天下午学习了二项式反演,做了一道错排的题,开始了苦逼的经历. 显然答案是C(︀n,k)︀*H(n − k).其中H(i)为长度为i的错排序列 然后经过课件上一番二项式反演的推导 我就写了个扩展卢卡斯 ...