isa
的做用,isa
的数据结构,isa
内各个位置内实际存储的内容,以及isa
在是不是nonpointer
下的区别isa
的走位图,SuperClass
的指向备注:isa是串联对象和类的重要线索,了解isa,能对对象的本质,类方法的走向等有更深入的理解复制代码
上篇说到,alloc
在开辟空间后也初始化了isa
,从而把对象和类关联起来。因此对于对象来讲,isa
的基础做用就是和类进行绑定,告诉系统对象的归属。可是大部分nonpointer
的isa
不只仅只是作指向,其内部还存储了大量的信息。bash
这里引入了一个nonpointer
的概念,简单说明下:数据结构
早期调用isa
能够直接返回类,后来苹果为了优化内存,使其内部增长了及其丰富的信息,而且增长了isa_mask
,不让直接获取类。有优化的就是提到的nonpointer
,也是本文研究的重点。
从初始化isa
的源码,来验证下这个说法
多线程
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
isa = newisa;
}
}复制代码
首先断言判断架构
assert(!isTaggedPointer());复制代码
若是是TaggedPointer
,后面就不执行,也就没有isa这个概念了。
app
这里引入了一个TaggedPointer
的概念:ide
NSNumber
对象 , 也会占用 8字节
内存 , 32位机器占用4字节。
为了存储和访问一个
NSNumber
对象,须要在堆上分配内存,另外还要维护它的引用计数,管理它的生命期 。这些都给程序增长了额外的逻辑,形成运行效率上的损失 。
所以若是没有额外处理 , 会形成很大空间浪费 .
所以苹果引入了TaggedPointer
,当对象为指针为TaggedPointer
类型时,指针的值不是地址了,而是真正的值,直接优化了存储,提高了获取速度。函数
TaggedPointer
的特色
优化
NSNumber
和部分NSString
malloc
和free
malloc
流程,获取时直接从地址提取值)回到源码来,ui
if (!nonpointer) {isa.cls = cls;}
spa
这里验证了,未开启isa
指针优化时,isa
直接和类关联,无后续操做;当开启优化时,先初始化了isa_t
isa_t newisa(0);复制代码
,而后对内部属性赋值,最后经过shiftcls
和类关联。
newisa.shiftcls = (uintptr_t)cls >> 3;复制代码
这能够说明isa_t
就是isa
真正的结构。
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
};复制代码
isa
的底层是isa_t
,isa_t
的结构是联合体+位域
。
回过头看,nonpointer
和!nonpointer
是只能二选一的,苹果就是利用这种互斥关系,把isa的结构定义成联合体
。追求内存极致优化的苹果显然不知足于此,在联合体
内又增长了位域
的结构来使isa
一应俱全。
看下ISA_BITFIELD
宏定义的每一个二进制位存储的内容(这里采用x86
架构下的结构,每种架构都有细微的差异,但所包含的内容是同样的,只是某些内容存储的长度不一致)
# 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复制代码
说明下各个存储位表明的意思:
nonpointer
:表示是否对 isa 指针开启指针优化( 0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等)
has_assoc
:关联对象标志位(0没有,1存在)
has_cxx_dtor
:该对象是否有 C++ 或者 Objc 的析构器,若是有析构函数,则须要作析构逻辑,若是没有,则能够更快的释放对象
shiftcls
:存储类指针的值。开启指针优化的状况下,在arm64架构下有33位用来存储类指针
magic
:用于调试器判断当前对象是真的对象仍是没有初始化的空间
weakly_referenced
:对象是否被指向或者曾经指向⼀个 ARC 的弱变量, 没有弱引⽤的对象能够更快释放。
deallocating
:标志对象是否正在释放内存
has_sidetable_rc
:当对象引⽤技术⼤于 10 时,则须要借⽤该变量存储进位
extra_rc
:当表示该对象的引⽤计数值,其实是引⽤计数值减 1, 例如,若是对象的引⽤计数为 10,那么 extra_rc 为 9。若是引⽤计数⼤于 10, 则须要使⽤到下⾯的 has_sidetable_rc。
CJPerson *object = [CJPerson alloc];
NSLog(@"object = %p", object);
CJPerson *object1 = [CJPerson alloc];
objc_setAssociatedObject(object1, @"object1", object1, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
NSLog(@"object1 = %p", object1);复制代码
用lldb
打印出它们各自的isa内容
能够看出,第一位是同样的,由于都是nonpointer
, 惟一的区别就是在第二位关联对象标志位。其余位置同理,有个比较特殊的就是shiftcls
,上文提到,早期isa
能够直接获取类,如今须要一个isa_mask
来间接获取。
看下object_getClass
底层,有一段这样的代码,也是经过isa_mask来获取isa
的指向
return (Class)(isa.bits & ISA_MASK);复制代码
x86
中的isa_mask
# define ISA_MASK 0x00007ffffffffff8ULL复制代码
转为二进制
0000 0000 0000 0000 0111 1111 1111 1111
1111 1111 1111 1111 1111 1111 1111 1000复制代码
这就很明显了,从第3位开始,后面的44位存储着shiftcls
的信息,恰好和上面给出的x86
下isa
的存储内容架构吻合。直接将对象的 isa & isa_mask
以后,就会获得对象的内存地址,也就是
isa
的指向。
这是苹果官方给出的isa
和superclass
的走位图。
举个例子验证下,用上面CJPerson
实例出来的对象lldb
打印下isa
的走位
x/4gx
打印的是对象在内存中从首地址开始,连续存储的4个8字节的内容地址,x/5gx
,x/6gx
依此类推。
p/t
、p/o
、p/d
、p/x
分别表明二进制、八进制、十进制和十六进制打印 。
由于isa
是对象中的第一个元素,因此x/4gx
打印出来的第一个地址就是isa
,在用isa&isa_mask
就获得isa
的指向,依次类推
在分别po打印下地址
得出结论:
总结:
isa
是串联对象,类,元类和根元类的重要线索,采用联合体加位域
的数据结构使有限的空间充分利用,存储了丰富的信息
以上就是关于isa的探索,后续继续更新类的底层结构,方法转发,block,锁,多线程等底层探索,还有应用程序加载,启动优化,内存优化等相关知识点,敬请关注。