iOS NSObject对象的本质、内存分配、ISA指针及superclass底层源码分析

本篇幅内容较多,但是干货满满,不仅涉及源码分析还涉及模拟系统底层计算分配流程,建议分次食用,耐心看完相信会有很多收获~

开发中使用最多的就是NSObject对象了,最近深入研究了一番,整理出来比较重要也是自己研究的比深入的几个点,通过源码的角度来分析一下,包括对象的底层实现,以及系统是如何使用内存对齐机制来计算对象大小的,包括isa指针及superclass指针等源码级别的分析,特做记录,以供翻阅回顾。

一 对象的本质

OC中的对象分为三种:

实例对象(instance对象)
存储实例变量的值

类对象(calss对象)
存储对象的信息、变量信息、实例方法、协议

元类对象(meta-class对象)
存储类方法

对象在底层是转变为c++的结构体来使用的,举个简单的例子看,比如创建一个Dog类,如下所示:

@interface Dog : NSObject
{
 int age;
 int ID;
 int number;
}
@end

@implementation Dog
@end

一个很简单的Dog类,当编译之后,他会被编译成如下的结构体:

struct Dog_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int age;
int ID;
int number;
};

很明显,这是一个结构体类型的数据,其中NSObject_IMPL类型是NSObject编译后的结构体,如下所示:

struct NSObject_IMPL{
Class isa;
}

明显NSObject里面的内容只有一个isa指针,isa指针的作用后面会分析。根据编译后的文件的内容来看,对象在运行的时候确实是以结构体的形式存在的。编译后的Dog结构体里有一个编译后的NSObject类型的结构体数据,因为它是继承于NSObject对象的,如果继承于其他对象的话也会有一个其他对象的结构体数据在里面。

顺便提一下,第一部分和第二部分都会用这个简单的Dog类来分析。

二 内存大小计算

还是以上面的Dog类来分析,看一下他在系统中占用的内存大小是多少,先自己计算一下(以64位系统分析):

Dog类编译后的结构体包含一个NSObject_IMPL类型的结构体数据,这个类型的结构体里有一个Class类型的变量,占用8个字节,所以Dog类里的NSObject_IMPL变量占用8个字节;
int类型的age变量占用4个字节;
int类型的ID变量占用4个字节;
int类型的number变量占用4个字节;
综上所属,Dog类型的数据应该占用8 + 4 + 4 + 4 = 20个字节。

看下系统的输出计算:

Dog类系统计算结果

可以看到系统给出的类的分配的空间大小为24字节(class_getInstanceSize方法),当实际使用的时候,给实例对象分配的大小达到了32字节(malloc_size方法),这个是为什么呢?

我们从源码的角度来对计算的方法一个个分析。

class_getInstanceSize
先看class_getInstanceSize方法。这个方法返回的是对类的实例变量分配的大小空间,而且是内存对齐之后的大小,看源码内容(苹果源码获取网站地址:https://opensource.apple.com/tarballs/):

class_getInstanceSize 源码

class_getInstanceSize

class_getInstanceSize源码

上面的三个图片是苹果objc框架的源码截图,展示了具体的class_getInstanceSize方法的实现,可以看到最终决定class_getInstanceSize大小的是word_align字节对齐方法,在这个方法里使用了字节对齐的方法来返回给类的变量实际分配的大小,我们根据方法的流程自己来算一下:
(x + WORD_MASK) & ~WORD_MASK;

内存分配计算

根据上面的计算流程,class_getInstanceSize其实是进行了一次8倍内存对齐的操作,所以为什么系统计算的class_getInstanceSize方法返回的是24自己想必各位已经很清楚了。

malloc_size
malloc_size方法返回的是对象的实例实际占用的内存大小,malloc_size和class_getInstanceSize一样也采用了内存对齐机制,只不过他用的是16倍的内存对齐机制,就不做具体分析了。相关源代码如下:

malloc_size

值得一提的是,如果我们计算NSObject大小的话,算然它的对象实例仅有一个8个字节的isa指针,但是我们会发现malloc_size方法返回的是16字节,原因如下:

malloc_size源码

OC的底层代码里对给对象分配的最小内存空间做了限制,限制最小为16字节,所以NSObject对象虽然仅仅有一个ISA指针,但是系统仍然会在实际使用他的实例对象的时候给他分配16个字节的空间。内存对齐机制是系统来决定的,这个机制提高了系统对内存空间的访问效率。

对象在内存中的排列

我们在看一下实例对象在使用的时候在内存中是如何存储的,为了方便内存查看,创建的一个dog对象并对其赋值,如下所示:


内存排列

根据对象的地址,我们看一下在内存中是如何排列的,以及占用的字节大小:

内存排列

每个数字代表一个字节,圈起来一共32的字节,正是malloc_size方法实际分配的字节数。

至此,对于对象在内存中的字节分配计算和使用应该已经非常清晰了吧。

三 ISA指针作用

ISA指针的作用是找到方法调用信息存储的对象,如果找到了就加以调用,我们引入一个SubDog的类来进行分析,此时我们有的类如下:

我们此时有两个类,一个继承于NSObject的Dog类,一个继承于Dog类的SubDog类,SubDog类里有一个实例方法和类方法,我们以SubDog类来分析一下他的实例对象,类对象,元类对象各自存储的信息是什么:

上面的图不仅仅列出来了subDog类的实例对象、类对象、元类对象的存储信息,还标明了Isa指针及superclass指针各自指向的地方。

再重新说明一下实例对象、类对象、元类对象内存储的信息:
实例对象(instance对象)
存储实例变量的值

类对象(calss对象)
存储对象的信息、变量信息、实例方法、协议

元类对象(meta-class对象)
存储类方法

我们看一个简单的实例,如果我们调用了

[subDog testInstanceFun];

这个方法,我们对这个方法的调用进行一个简单的分析:

1.从面向对象的角度来分析,我们创建了一个subDog实例对象,这个对象实现了testInstanceFun实例方法,所以我们调用testInstanceFun是没有问题的

2.从ISA指向的角度来分析:当我们调用subDog的testInstanceFun实例方法的时候,实际上是先通过subDog对象的isa指针寻找到SubDog类对象,SubDog类对象里包含了testInstanceFun方法的信息,所以会直接调用testInstanceFun方法。

这个就是ISA指针在方法调用里的作用。

ISA指针深入分析

实际上ISA指针里存储的就是指向对象的内存地址,我们验证一下实例对象的ISA指针是否是指向其类对象的:

创建一个SubDog的类对象和SubDog的实例对象,并打印输出他们的地址和ISA指针(因为无法直接打印对象的ISA指针,所以我做了一个特殊处理然后将实例对象的ISA指针打印了出来):

可以看到 :
SubDog类对象地址是 0x00000001000022c0
SubDog实例对象地址是 0x001d8001000022c1

他俩的值并不一样,为什么呢?
因为在ios的系统中如果需要通过ISA的地址进行查找的话,需要使用ISA_MASK进行&的操作之后才可以得到真正的地址,

我们试一下:

可以看到SubDog的实例对象通过进行和ISA_MASK的&操作之后得到的就是SubDog类对象地址 0x00000001000022c0

因此,我们可以得知实例对象的ISA指针确实是指向其类对象的,类对象的ISA指向元类对象也是一样的。

扩展:
object_getClass 也是根据ISA指针来返回数据的:
如果传入的是一个实例对象,那么就会返回类对象;
如果传入的是类对象,那么返回的就是元类对象;
如果传入的是元类对象,那么返回的就是元类对象的基类对象。
看源码表述的也很清晰:


object_getClass

objc_getClass 则是根据传入的字符串作为key去一个map表里查找,看是否有跟传入的key一致的类并将其返回,如果没有匹配的话则返回空。

objc_getClass的源码里调用的关键方法是:

四 superclass的作用

superclass从面向对象的角度来看的话,概念和原理是比较清晰的:调用方法时会从自己的方法实现里去找,如果没有实现则去父类里去寻找,如果父类一层层也没实现的话会抛出一个unrecognized异常,如果把这个流程放在我们的图里看的话就是这样的:

superclass

从实例对象开始->寻找实例对象的类对象看是否有方法信息->根据superclass寻找父类类对象信息->根据superclass找到NSObjct类对象->nil

还是验证一下SubDog的superclass指向的是否是Dog类:


很明显SubDog的类对象superclass的值和其父类Dog类对象的地址是一致的。

再看一个比较有意思的例子:

创建一个NSObject的category,并且给他添加一个实例方法,如下:

将这个category引入到我们的测试文件后,然后我们直接调用:

这个时候会发生什么呢?
直接看结果:

一个类对象,居然直接调用成功了父类对象的实例方法,如果从面向对象的角度来看的话是很难说通的吧?那我们通过isa指针和supperclass指针结合来看一下就很清晰了,如下图:

文字说明一下整个流程:
SubDog类对象通过ISA寻找SubDog元类对象看是否有方法实现->SubDog元类对象通过supperclass指针寻找Dog元类对象是否有方法实现->Dog元类对象通过supperclass寻找到NSObject元类对象->(特殊)NSObject元类对象的supperclass是NSObject的类对象,NSObject元类对象通过supperclass找到NSObject的类对象,NSObject的类对象里记录了我们添加的实例方法testNSObjectInstanceFun的实现信息,所以就可以直接调用成功了。

其实我们都知道,方法的调用是通过Objc 发送消息的机制来进行消息传递然后调用的。实际上在消息发送的时候并没有标记这个消息方法是+号消息还是-号消息(类方法或者实例方法),不管是类方法还是实例方法都是通过方法名去找,所以我们通过类对象直接调用实例方法是可以实现的。

附一张网络图:


小结:

至此我们的内容介绍就全部结束了,有问题欢迎留言,我们一起探讨~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345