在 alloc的初探 中了解了如何获取对象大小,内存对齐的原则,对象的 alloc
,可是在调用 calloc
在堆上开辟一个内存空间时返回了一个指针地址,这时候咱们如何将这个指针地址和当前对象关联呢?bash
下方源码中 initInstanceIsa
就干的是这些事情。数据结构
//...
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
//...
复制代码
简单点来讲,其实就是英文 is a
写到一块儿了,说明某一个对象是什么。好比: object is a NSObject
,可是 isa
通过发展以后存储的东西变得至关的庞大。架构
官方对 isa
的解释为: 每一个对象都是经过 isa
实例变量链接到运行时系统,从 NSObject
类继承。Isa
标识对象的类;它指向一个结构的类定义编译。ide
经过 ISA
,能够在运行时找到一个对象的全部信息,如继承层次结构中的位置,它的实例变量的大小和结构,以及能够相应消息的方法所实现的位置。函数
进入 initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
函数以后发现,isa
是一个 isa_t
的类型。post
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
是位域。ui
联合体又被称为共用体,顾名思义就是在 union
定义下的变量共用一块内存单元,赋值时相互覆盖,这种结构被称为联合体。spa
有些信息在存储时,并不须要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有 0 和 1 两种状态, 用一位二进位便可。debug
为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为 位域 或 位段。指针
所谓 位域 是把一个字节中的二进位划分为几 个不一样的区域,并说明每一个区域的位数。每一个域有一个域名,容许在程序中按域名进行操做。 这样就能够把几个不一样的对象用一个字节的二进制位域来表示。
位段成员必须声明为 int
、unsigned int
或 signed int
类型(short char long
)。
位域列表定义的形式为: 类型说明符 位域名:位域长度
struct 位域结构名
{
位域列表
};
复制代码
例如:
struct bits
{
int a:8;
int b:2;
int c:6;
};
复制代码
说明 bits
,共占两个字节。其中位域 a
占 8
位,位域 b
占 2
位,位域 c
占 6
位。
isa_t
中 bit
是 unsigned long
类型,占用 8
个字节,也就是 8byte = 64 bit
,因此在 __arm64__
和 __x86_64__
下 isa_t 的位域大小都是 64
个 bit
,只是由于架构不一样因此占得位数不一样罢了。
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];
建立一个对象的时候,object
的 isa
已经建立完毕了,而且将对象的类写入了 shiftcls
。
用 x/4gx
以16进制打印4个8字节的内存值,那么第一个必定是 isa
。
p/t
以二进制打印 isa
的内存值 0x001d800100001139
,获取到64个 bit
的二进制。p/x TestClass.class
打印 TestClass
的类, 获取指针地址。(uintptr_t)cls >> 3
还原。最后比对结果是同样的。 $2 = 0b0000000000000000000000000000000100000000000000000001000100111000
$7 = 0b0000000000000000000000000000000100000000000000000001000100111000
在 objc_class()
函数里就有对类的返回,使用的蒙版取 3~48 位。
inline Class
objc_object::ISA()
{
//...
//这里是返回类对象须要用 isa 的指针 & ISA_MASK
return (Class)(isa.bits & ISA_MASK);
}
复制代码
验证以下:
既然已经知道了对象的 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
复制代码
发现类对象的地址都是相同的,说明类对象在内存中有且只能存在一个。
在对象返回前,会给 isa
进行赋值,标识对象属于什么类,对象指向的类其实也是一个对象,这种对象被称为类对象。
既然是对象那一定存在 isa
,那么类对象的 isa
又指向什么呢?
x/4gx
打印类的内存结构:
打印出来的内存结构,其实都是内存的值,真正指向当前对象的指针地址是最前面的 0x100001138
,po 0x100001138
发现这个对象居然也是 TestClass
类型的,这个 TestClass
实际上是指向类对象的类,又称为元类。
元类是系统建立的,当程序中有一个类被定义,在编译器编译时,会相应的生成一个指向类对象的元类,以便以保存类的一些相关信息,好比:类方法等。
咱们知道了对象的 isa
指向类对象,类对象的 isa
指向元类,那么元类的 isa
指向什么呢?
接下来使用上方证实对象的 isa
指向的方法来推导一下元类的 isa
的指向。
推导过程以下:
从上图能看到 TestClass
的类对象的 isa
指向 TestClass
的元类。
继续查看 TestClass
元类的 isa
指向的是 NSObject
,那这个 NSObject
究竟是元类仍是类对象呢?
由于内存中类对象只有一个,类对象指针地址惟一,因此若是 NSObject.class
的指针地址和 TestClass
元类的 isa
指向地址相同则说明是 isa
指向 NSObject
类,不然不是。
继续查看 NSObject
类的内存地址, p/x NSObject.class = 0x0000000100b38140 NSObject
,很遗憾并非 NSObject
的类对象。
查看 NSObject
类对象的元类,发现了 NSObject
元类的指针地址和 TestClass
元类 isa
指向的地址是相同的,这就说明了 TestClass
元类的 isa
指向的是 NSObject
元类。
继续查看 NSObject
元类 isa
的指向,发现指向的是本身。
因此就有了苹果老大给的下方的图。
在上方的指向中,subclass
的 meta class
直接指向了 NSObject
的 meta class
,是由于元类保存的就是类的信息,能够说已经到头了, 若是再次继承 Super meta class
已经没有什么意义,而且还会让继承关系更加复杂,使得继承树更加难以维护,因此苹果将 subclass
的 meta class
直接指向了 NSObject
的 meta class
,再将 NSObject
的 meta class
指向了本身造成了一个闭环。
以上就是 isa
的初始化过程和指向分析。