isa的初始化&指向分析

alloc的初探 中了解了如何获取对象大小,内存对齐的原则,对象的 alloc,但是在调用 calloc 在堆上开辟一个内存空间时返回了一个指针地址,这时候我们如何将这个指针地址和当前对象关联呢?

下方源码中 initInstanceIsa 就干的是这些事情。

//...
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
//...

1、什么是 isa?

简单点来说,其实就是英文 is a 写到一起了,说明某一个对象是什么。比如: object is a NSObject,但是 isa 经过发展之后存储的东西变得相当的庞大。

官方对 isa 的解释为:
每个对象都是通过 isa 实例变量连接到运行时系统,从 NSObject 类继承。Isa 标识对象的类;它指向一个结构的类定义编译。

通过 ISA,可以在运行时找到一个对象的所有信息,如继承层次结构中的位置,它的实例变量的大小和结构,以及可以相应消息的方法所实现的位置。

2、isa 存储结构分析

进入 initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 函数之后发现,isa 是一个 isa_t 的类型。

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
# if __arm64__
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19

# elif __x86_64__
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8

上方代码可以看到 isa_t 类型是一个 union 联合体。ISA_BITFIELD 是位域。

1、什么联合体?

联合体又被称为共用体,顾名思义就是在 union 定义下的变量共用一块内存单元,赋值时相互覆盖,这种结构被称为联合体。

2、什么是位域?

有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有 0 和 1 两种状态, 用一位二进位即可。

为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为 位域位段

所谓 位域 是把一个字节中的二进位划分为几 个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。

位段成员必须声明为 intunsigned intsigned int 类型(short char long)。

3、位域如何定义?

位域列表定义的形式为: 类型说明符 位域名:位域长度

struct 位域结构名     
{ 
    位域列表 
};  

例如:

struct bits     
{     
    int a:8;     
    int b:2;     
    int c:6;     
};  

说明 bits,共占两个字节。其中位域 a8 位,位域 b2 位,位域 c6 位。

4、isa_t 的位域

isa_tbitunsigned long 类型,占用 8 个字节,也就是 8byte = 64 bit ,所以在 __arm64____x86_64__ 下 isa_t 的位域大小都是 64bit,只是因为架构不同所以占得位数不同罢了。

image.png

3、isa 的初始化

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    //...

    if (!nonpointer) {
        isa.cls = cls;
    } else {
        isa_t newisa(0);

        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;

        //...

        isa = newisa;
    }
}

在初始化 isa 的时候会将 alloc 出来的对象和类进行绑定 obj -> isa -> shiftcls

当执行 TestClass *object = [TestClass alloc]; 创建一个对象的时候,objectisa 已经创建完毕了,并且将对象的类写入了 shiftcls

1、对 shiftcls 使用二进制验证

x/4gx 以16进制打印4个8字节的内存值,那么第一个一定是 isa

object 内存分布
  • p/t 以二进制打印 isa 的内存值 0x001d800100001139 ,获取到64个 bit 的二进制。
  • p/x TestClass.class 打印 TestClass 的类, 获取指针地址。
  • 根据 (uintptr_t)cls >> 3 还原。
还原推导过程

最后比对结果是一样的。
$2 = 0b0000000000000000000000000000000100000000000000000001000100111000
$7 = 0b0000000000000000000000000000000100000000000000000001000100111000

2、对 shiftcls 使用16进制蒙版验证

objc_class() 函数里就有对类的返回,使用的蒙版取 3~48 位。

inline Class 
objc_object::ISA() 
{
    //...

    //这里是返回类对象需要用 isa 的指针 & ISA_MASK
    return (Class)(isa.bits & ISA_MASK);
}

验证如下:

image.png

4、类在内存中存在的个数

既然已经知道了对象的 isa 指向了类,那类到底是怎样的呢?对象在实例化的时候,每个对象都是不同的,其指针地址也是不相同的,那么类呢?能否在内存中存在多份?

下方验证一下:

Class class1 = [TestClass class];
Class class2 = [TestClass alloc].class;
Class class3 = object_getClass([TestClass alloc]);
Class class4 = [TestClass alloc].class;
        
NSLog(@"\n%p \n%p \n%p \n%p \n",class1,class2,class3,class4);

打印结果过下:
2019-12-22 15:33:01.539549+0800 objc-debug[3353:135796] 
0x100001170 
0x100001170 
0x100001170 
0x100001170

发现类对象的地址都是相同的,说明类对象在内存中有且只能存在一个。

5、isa 的指向分析

在对象返回前,会给 isa 进行赋值,标识对象属于什么类,对象指向的类其实也是一个对象,这种对象被称为类对象。

既然是对象那必定存在 isa ,那么类对象的 isa 又指向什么呢?

x/4gx打印类的内存结构:

类的内存结构

打印出来的内存结构,其实都是内存的值,真正指向当前对象的指针地址是最前面的 0x100001138po 0x100001138 发现这个对象竟然也是 TestClass 类型的,这个 TestClass其实是指向类对象的类,又称为元类。

image.png

元类是系统创建的,当程序中有一个类被声明,在编译器编译时,会相应的生成一个指向类对象的元类,以便以保存类的一些相关信息,比如:类方法等。

我们知道了对象的 isa 指向类对象,类对象的 isa 指向元类,那么元类的 isa 指向什么呢?

接下来使用上方证明对象的 isa 指向的方法来推导一下元类的 isa 的指向。

推导过程如下:

image.png

从上图能看到 TestClass 的类对象的 isa 指向 TestClass的元类。

image.png

继续查看 TestClass 元类的 isa 指向的是 NSObject ,那这个 NSObject 到底是元类还是类对象呢?

因为内存中类对象只有一个,类对象指针地址唯一,所以如果 NSObject.class 的指针地址和 TestClass 元类的 isa 指向地址相同则说明是 isa 指向 NSObject 类,否则不是。

image.png

继续查看 NSObject 类的内存地址, p/x NSObject.class = 0x0000000100b38140 NSObject,很遗憾并不是 NSObject 的类对象。

查看 NSObject 类对象的元类,发现了 NSObject 元类的指针地址和 TestClass 元类 isa 指向的地址是相同的,这就说明了 TestClass 元类的 isa 指向的是 NSObject 元类。

继续查看 NSObject 元类 isa 的指向,发现指向的是自己。

image.png

所以就有了苹果老大给的下方的图。


isa 的指向图

6、isa 的指向补充

在上方的指向中,subclassmeta class 直接指向了 NSObjectmeta class ,是因为元类保存的就是类的信息,可以说已经到头了, 如果再次继承 Super meta class 已经没有什么意义,并且还会让继承关系更加复杂,使得继承树更加难以维护,所以苹果将 subclassmeta class 直接指向了 NSObjectmeta class ,再将 NSObjectmeta class 指向了自己形成了一个闭环。

以上就是 isa 的初始化过程和指向分析。

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

推荐阅读更多精彩内容