本文的主要目的是理解isa指针相关的一些知识。
我们都知道oc对象的本质是一个结构体,想要更好的了解这个结构体,我们就需要用到apple主导编写的编译器clang,它是一个基于LLVM的C/C++/OC的编译器,主要是用于底层编译,将文件输出成c++文件,例如main.m 输出成main.cpp,其目的是为了更好的观察底层的一些结构 及 实现的逻辑,方便理解底层原理。
具体使用可参考:https://www.jianshu.com/p/9fc7776cce9b
在main中自定义一个类LGPerson,有一个属性name,通过终端,利用clang将main.m编译成 main.cpp
clang编译main.m.jpg
打开编译好的main.cpp,找到LGPerson的定义,发现LGPerson在底层会被编译成 struct 结构体。
LGPerson_IMPL中的第一个属性 其实就是 isa,是继承自NSObject,属于伪继承,伪继承的方式是直接将NSObject结构体定义为LGPerson中的第一个属性,意味着LGPerson 拥有 NSObject中的所有成员变量。
LGPerson中的第一个属性 NSObject_IVARS 等效于 NSObject中的 isa。
之前的博客中有画过一个alloc的底层执行流程分析图,我们来看看核心之一的initInstanceIsa方法,通过查看这个方法的源码实现,我们发现,isa指针的初始化,是通过isa_t这个类型来完成的,isa_t是使用联合体定义union,而在NSObject定义中isa的类型是Class,其根本原因是由于isa 对外反馈的是类信息,为了让开发人员更加清晰明确,需要在isa返回时做了一个类型的强制转换。
//initInstanceIsa
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
//初始化isa
initIsa(cls, true, hasCxxDtor);
}
//isa_t的定义
union isa_t { //联合体
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//提供了cls 和 bits ,两者是互斥关系
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
//isa返回时做的强制转换
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);//强制转换
#endif
}
联合体相比结构体,它也是由不同的数据类型组成,但其变量是互斥的,所有的成员共占一段内存。而且共用体采用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会将原来成员的值覆盖。
isa_t类型使用联合体的原因也是基于内存优化的考虑,这里的内存优化是指在isa指针中通过char + 位域(即二进制中每一位均可表示不同的信息)的原理实现。通常来说,isa指针占用的内存大小是8字节,即64位,已经足够存储很多的信息了,这样可以极大的节省内存,以提高性能。
从isa_t的定义中可以看出:
- isa_t提供了两个成员,cls 和 bits,由联合体的定义所知,这两个成员是互斥的,也就意味着,当初始化isa指针时,有两种初始化方式:
一: 通过cls初始化,bits无默认值
二: 通过bits初始化,cls有默认值 - 还提供了一个结构体定义的位域,用于存储类信息及其他信息,结构体的成员ISA_BITFIELD,这是一个宏定义,有两个版本 arm64(对应ios 移动端) 和 x86_64(对应macOS),以下是它们的一些宏定义:
位域作用域.png
通过initInstanceIsa进入其中的initIsa方法,其功能主要是初始化isa指针
isa初始化.png
从源码可以看出,两种初始化方式,cls和bits
而且也可看出cls 与 isa 关联原理,就是在isa指针中的shiftcls位域中存储了类的信息,将cls进行编码后,将其转换为uintptr_t数据类型,使其可以被机器码识别,然后由于shiftcls处于isa指针地址的中间部分,前面还有3个位域,为了不影响前面的3个位域的数据,需要右移将其抹零。
网友评论